jira-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJira Integration Mastery
Jira集成精通指南
This skill provides comprehensive guidance for integrating with Jira using the Atlassian CLI (acli), Jira REST API, and ADF (Atlassian Document Format). Essential for automating issue management, sprint planning, JQL queries, and building robust Jira-based workflows.
本技能提供使用Atlassian CLI(acli)、Jira REST API和ADF(Atlassian文档格式)进行Jira集成的全面指导,是自动化问题管理、Sprint规划、JQL查询以及构建可靠的Jira工作流的必备内容。
When to Use This Skill
何时使用本技能
- Automating Jira issue creation, updates, and transitions
- Building sprint planning workflows
- Writing and executing JQL queries
- Formatting comments with ADF (Atlassian Document Format)
- Parsing Jira URLs and extracting metadata
- Integrating Jira into development automation
- Creating bulk operations across multiple issues
- 自动化Jira问题的创建、更新和状态流转
- 构建Sprint规划工作流
- 编写并执行JQL查询
- 使用ADF(Atlassian文档格式)格式化评论
- 解析Jira URL并提取元数据
- 将Jira集成到开发自动化流程中
- 对多个问题执行批量操作
Atlassian CLI (acli) Mastery
Atlassian CLI(acli)精通指南
The Atlassian CLI () is the primary tool for Jira automation via command line.
acliAtlassian CLI()是通过命令行实现Jira自动化的主要工具。
acliInstallation and Configuration
安装与配置
bash
undefinedbash
undefinedDownload acli
下载acli
Extract and setup
解压并设置
unzip acli-9.8.0-distribution.zip
export PATH=$PATH:/path/to/acli
unzip acli-9.8.0-distribution.zip
export PATH=$PATH:/path/to/acli
Configure connection
配置连接
acli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action getServerInfo
acli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action getServerInfo
Store credentials (creates ~/.acli/acli.properties)
存储凭据(将创建 ~/.acli/acli.properties)
acli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action login
**Configuration file** (`~/.acli/acli.properties`):
```properties
server=https://your-domain.atlassian.net
user=user@example.com
password=your-api-tokenacli jira --server https://your-domain.atlassian.net --user user@example.com --password your-api-token --action login
**配置文件**(`~/.acli/acli.properties`):
```properties
server=https://your-domain.atlassian.net
user=user@example.com
password=your-api-tokenIssue Operations
问题操作
List issues:
bash
undefined列出问题:
bash
undefinedList issues in project
列出项目中的问题
acli jira --action getIssueList --project PROJ
acli jira --action getIssueList --project PROJ
List with JQL
使用JQL列出问题
acli jira --action getIssueList --jql "project = PROJ AND status = 'To Do'"
acli jira --action getIssueList --jql "project = PROJ AND status = 'To Do'"
List with specific fields
列出指定字段的问题
acli jira --action getIssueList --jql "assignee = currentUser()" --outputFormat 2 --columns "key,summary,status"
**Get issue details**:
```bashacli jira --action getIssueList --jql "assignee = currentUser()" --outputFormat 2 --columns "key,summary,status"
**获取问题详情**:
```bashGet full issue details
获取完整的问题详情
acli jira --action getIssue --issue PROJ-123
acli jira --action getIssue --issue PROJ-123
Get specific fields
获取指定字段的问题详情
acli jira --action getIssue --issue PROJ-123 --outputFormat 2 --columns "key,summary,description,status,assignee"
**Create issue**:
```bashacli jira --action getIssue --issue PROJ-123 --outputFormat 2 --columns "key,summary,description,status,assignee"
**创建问题**:
```bashCreate issue
创建问题
acli jira --action createIssue
--project PROJ
--type "Story"
--summary "Implement authentication"
--description "Add OAuth2 authentication to the application"
--priority "High"
--labels "backend,security"
--project PROJ
--type "Story"
--summary "Implement authentication"
--description "Add OAuth2 authentication to the application"
--priority "High"
--labels "backend,security"
acli jira --action createIssue
--project PROJ
--type "Story"
--summary "实现认证功能"
--description "为应用添加OAuth2认证"
--priority "High"
--labels "backend,security"
--project PROJ
--type "Story"
--summary "实现认证功能"
--description "为应用添加OAuth2认证"
--priority "High"
--labels "backend,security"
Create with custom fields
创建带自定义字段的问题
acli jira --action createIssue
--project PROJ
--type "Bug"
--summary "Login fails on mobile"
--field "customfield_10001=High Priority"
--project PROJ
--type "Bug"
--summary "Login fails on mobile"
--field "customfield_10001=High Priority"
**Update issue**:
```bashacli jira --action createIssue
--project PROJ
--type "Bug"
--summary "移动端登录失败"
--field "customfield_10001=High Priority"
--project PROJ
--type "Bug"
--summary "移动端登录失败"
--field "customfield_10001=High Priority"
**更新问题**:
```bashUpdate summary and description
更新摘要和描述
acli jira --action updateIssue
--issue PROJ-123
--summary "Updated summary"
--description "Updated description"
--issue PROJ-123
--summary "Updated summary"
--description "Updated description"
acli jira --action updateIssue
--issue PROJ-123
--summary "更新后的摘要"
--description "更新后的描述"
--issue PROJ-123
--summary "更新后的摘要"
--description "更新后的描述"
Update custom fields
更新自定义字段
acli jira --action updateIssue
--issue PROJ-123
--field "customfield_10001=New Value"
--issue PROJ-123
--field "customfield_10001=New Value"
acli jira --action updateIssue
--issue PROJ-123
--field "customfield_10001=新值"
--issue PROJ-123
--field "customfield_10001=新值"
Add labels
添加标签
acli jira --action updateIssue
--issue PROJ-123
--labels "bug,urgent"
--labelsAdd
--issue PROJ-123
--labels "bug,urgent"
--labelsAdd
**Transition issue**:
```bashacli jira --action updateIssue
--issue PROJ-123
--labels "bug,urgent"
--labelsAdd
--issue PROJ-123
--labels "bug,urgent"
--labelsAdd
**流转问题状态**:
```bashMove to different status
切换到不同状态
acli jira --action transitionIssue
--issue PROJ-123
--transition "In Progress"
--issue PROJ-123
--transition "In Progress"
acli jira --action transitionIssue
--issue PROJ-123
--transition "In Progress"
--issue PROJ-123
--transition "In Progress"
Transition with comment
带评论的状态流转
acli jira --action transitionIssue
--issue PROJ-123
--transition "Done"
--comment "Completed implementation and testing"
--issue PROJ-123
--transition "Done"
--comment "Completed implementation and testing"
**Assign issue**:
```bashacli jira --action transitionIssue
--issue PROJ-123
--transition "Done"
--comment "已完成开发和测试"
--issue PROJ-123
--transition "Done"
--comment "已完成开发和测试"
**分配问题**:
```bashAssign to user
分配给指定用户
acli jira --action assignIssue
--issue PROJ-123
--assignee "john.doe"
--issue PROJ-123
--assignee "john.doe"
acli jira --action assignIssue
--issue PROJ-123
--assignee "john.doe"
--issue PROJ-123
--assignee "john.doe"
Assign to me
分配给自己
acli jira --action assignIssue
--issue PROJ-123
--assignee "@me"
--issue PROJ-123
--assignee "@me"
undefinedacli jira --action assignIssue
--issue PROJ-123
--assignee "@me"
--issue PROJ-123
--assignee "@me"
undefinedSprint Operations
Sprint操作
List sprints:
bash
undefined列出Sprint:
bash
undefinedList sprints for board
列出看板的Sprint
acli jira --action getSprintList
--board "PROJ Board"
--board "PROJ Board"
acli jira --action getSprintList
--board "PROJ Board"
--board "PROJ Board"
List active sprints
列出活跃Sprint
acli jira --action getSprintList
--board "PROJ Board"
--state "active"
--board "PROJ Board"
--state "active"
**Add issues to sprint**:
```bashacli jira --action getSprintList
--board "PROJ Board"
--state "active"
--board "PROJ Board"
--state "active"
**添加问题到Sprint**:
```bashAdd single issue
添加单个问题
acli jira --action addIssuesToSprint
--sprint "Sprint 24"
--issue "PROJ-123"
--sprint "Sprint 24"
--issue "PROJ-123"
acli jira --action addIssuesToSprint
--sprint "Sprint 24"
--issue "PROJ-123"
--sprint "Sprint 24"
--issue "PROJ-123"
Add multiple issues
添加多个问题
acli jira --action addIssuesToSprint
--sprint "Sprint 24"
--issue "PROJ-123,PROJ-124,PROJ-125"
--sprint "Sprint 24"
--issue "PROJ-123,PROJ-124,PROJ-125"
**Start sprint**:
```bashacli jira --action addIssuesToSprint
--sprint "Sprint 24"
--issue "PROJ-123,PROJ-124,PROJ-125"
--sprint "Sprint 24"
--issue "PROJ-123,PROJ-124,PROJ-125"
**启动Sprint**:
```bashStart sprint with date range
指定日期范围启动Sprint
acli jira --action startSprint
--sprint "Sprint 24"
--startDate "2024-01-01"
--endDate "2024-01-14"
--sprint "Sprint 24"
--startDate "2024-01-01"
--endDate "2024-01-14"
**Close sprint**:
```bashacli jira --action startSprint
--sprint "Sprint 24"
--startDate "2024-01-01"
--endDate "2024-01-14"
--sprint "Sprint 24"
--startDate "2024-01-01"
--endDate "2024-01-14"
**关闭Sprint**:
```bashComplete sprint (moves incomplete issues to backlog)
完成Sprint(将未完成问题移至待办)
acli jira --action completeSprint
--sprint "Sprint 24"
--sprint "Sprint 24"
undefinedacli jira --action completeSprint
--sprint "Sprint 24"
--sprint "Sprint 24"
undefinedBoard Management
看板管理
List boards:
bash
undefined列出看板:
bash
undefinedList all boards
列出所有看板
acli jira --action getBoardList
acli jira --action getBoardList
List boards for project
列出项目的看板
acli jira --action getBoardList
--project PROJ
--project PROJ
**Get board configuration**:
```bashacli jira --action getBoardList
--project PROJ
--project PROJ
**获取看板配置**:
```bashGet board details
获取看板详情
acli jira --action getBoard
--board "PROJ Board"
--board "PROJ Board"
undefinedacli jira --action getBoard
--board "PROJ Board"
--board "PROJ Board"
undefinedBulk Operations
批量操作
Bulk transition:
bash
undefined批量流转状态:
bash
undefinedTransition multiple issues
批量流转问题状态
acli jira --action progressIssue
--issue "PROJ-123,PROJ-124,PROJ-125"
--transition "In Progress"
--issue "PROJ-123,PROJ-124,PROJ-125"
--transition "In Progress"
**Bulk update**:
```bashacli jira --action progressIssue
--issue "PROJ-123,PROJ-124,PROJ-125"
--transition "In Progress"
--issue "PROJ-123,PROJ-124,PROJ-125"
--transition "In Progress"
**批量更新**:
```bashUpdate multiple issues
批量更新问题
acli jira --action updateIssue
--issue "PROJ-123,PROJ-124"
--labels "sprint-24"
--labelsAdd
--issue "PROJ-123,PROJ-124"
--labels "sprint-24"
--labelsAdd
For complete acli command reference, see `references/acli-reference.md`.
---acli jira --action updateIssue
--issue "PROJ-123,PROJ-124"
--labels "sprint-24"
--labelsAdd
--issue "PROJ-123,PROJ-124"
--labels "sprint-24"
--labelsAdd
完整的acli命令参考,请查看`references/acli-reference.md`。
---Jira REST API Patterns
Jira REST API模式
API Basics
API基础
typescript
import axios from 'axios';
// Configure API client
const jiraClient = axios.create({
baseURL: 'https://your-domain.atlassian.net/rest/api/3',
auth: {
username: 'user@example.com',
password: 'your-api-token'
},
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});typescript
import axios from 'axios';
// 配置API客户端
const jiraClient = axios.create({
baseURL: 'https://your-domain.atlassian.net/rest/api/3',
auth: {
username: 'user@example.com',
password: 'your-api-token'
},
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});Issue CRUD Operations
问题CRUD操作
Get issue:
typescript
const response = await jiraClient.get(`/issue/PROJ-123`);
const issue = response.data;
console.log(issue.key);
console.log(issue.fields.summary);
console.log(issue.fields.status.name);Create issue:
typescript
const newIssue = await jiraClient.post('/issue', {
fields: {
project: {
key: 'PROJ'
},
summary: 'Implement authentication',
description: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Add OAuth2 authentication'
}
]
}
]
},
issuetype: {
name: 'Story'
},
priority: {
name: 'High'
},
labels: ['backend', 'security']
}
});
console.log(`Created issue: ${newIssue.data.key}`);Update issue:
typescript
await jiraClient.put(`/issue/PROJ-123`, {
fields: {
summary: 'Updated summary',
labels: ['bug', 'urgent']
}
});Transition issue:
typescript
// Get available transitions
const transitionsResp = await jiraClient.get(`/issue/PROJ-123/transitions`);
const transitions = transitionsResp.data.transitions;
// Find "In Progress" transition
const inProgressTransition = transitions.find(t => t.name === 'In Progress');
// Execute transition
await jiraClient.post(`/issue/PROJ-123/transitions`, {
transition: {
id: inProgressTransition.id
}
});获取问题:
typescript
const response = await jiraClient.get(`/issue/PROJ-123`);
const issue = response.data;
console.log(issue.key);
console.log(issue.fields.summary);
console.log(issue.fields.status.name);创建问题:
typescript
const newIssue = await jiraClient.post('/issue', {
fields: {
project: {
key: 'PROJ'
},
summary: '实现认证功能',
description: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '添加OAuth2认证'
}
]
}
]
},
issuetype: {
name: 'Story'
},
priority: {
name: 'High'
},
labels: ['backend', 'security']
}
});
console.log(`已创建问题: ${newIssue.data.key}`);更新问题:
typescript
await jiraClient.put(`/issue/PROJ-123`, {
fields: {
summary: '更新后的摘要',
labels: ['bug', 'urgent']
}
});流转问题状态:
typescript
// 获取可用的状态流转
const transitionsResp = await jiraClient.get(`/issue/PROJ-123/transitions`);
const transitions = transitionsResp.data.transitions;
// 找到"In Progress"状态流转
const inProgressTransition = transitions.find(t => t.name === 'In Progress');
// 执行状态流转
await jiraClient.post(`/issue/PROJ-123/transitions`, {
transition: {
id: inProgressTransition.id
}
});Advanced Operations
高级操作
Add comment:
typescript
await jiraClient.post(`/issue/PROJ-123/comment`, {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This issue has been reviewed and approved'
}
]
}
]
}
});Add attachment:
typescript
import FormData from 'form-data';
import fs from 'fs';
const form = new FormData();
form.append('file', fs.createReadStream('screenshot.png'));
await jiraClient.post(`/issue/PROJ-123/attachments`, form, {
headers: {
...form.getHeaders(),
'X-Atlassian-Token': 'no-check'
}
});Link issues:
typescript
await jiraClient.post('/issueLink', {
type: {
name: 'Blocks'
},
inwardIssue: {
key: 'PROJ-123'
},
outwardIssue: {
key: 'PROJ-456'
}
});For complete API patterns and examples, see .
references/jira-api-patterns.md添加评论:
typescript
await jiraClient.post(`/issue/PROJ-123/comment`, {
body: {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '该问题已审核通过'
}
]
}
]
}
});添加附件:
typescript
import FormData from 'form-data';
import fs from 'fs';
const form = new FormData();
form.append('file', fs.createReadStream('screenshot.png'));
await jiraClient.post(`/issue/PROJ-123/attachments`, form, {
headers: {
...form.getHeaders(),
'X-Atlassian-Token': 'no-check'
}
});关联问题:
typescript
await jiraClient.post('/issueLink', {
type: {
name: 'Blocks'
},
inwardIssue: {
key: 'PROJ-123'
},
outwardIssue: {
key: 'PROJ-456'
}
});完整的API模式和示例,请查看。
references/jira-api-patterns.mdJQL (Jira Query Language)
JQL(Jira查询语言)
JQL is Jira's query language for searching and filtering issues.
JQL是Jira用于搜索和筛选问题的查询语言。
Basic JQL Syntax
基础JQL语法
jql
undefinedjql
undefinedSingle condition
单个条件
project = PROJ
project = PROJ
Multiple conditions (AND)
多个条件(AND)
project = PROJ AND status = "To Do"
project = PROJ AND status = "To Do"
Multiple conditions (OR)
多个条件(OR)
status = "To Do" OR status = "In Progress"
status = "To Do" OR status = "In Progress"
Negation
取反
status != Done
status != Done
IN operator
IN操作符
status IN ("To Do", "In Progress")
status IN ("To Do", "In Progress")
Comparison
比较
created >= -7d
undefinedcreated >= -7d
undefinedCommon JQL Queries
常用JQL查询
By status:
jql
undefined按状态:
jql
undefinedOpen issues
未关闭的问题
status IN ("To Do", "In Progress", "Review")
status IN ("To Do", "In Progress", "Review")
Closed issues
已关闭的问题
status = Done
status = Done
Not done
未完成的问题
status != Done
**By assignee**:
```jqlstatus != Done
**按经办人**:
```jqlAssigned to me
我的问题
assignee = currentUser()
assignee = currentUser()
Unassigned
未分配的问题
assignee IS EMPTY
assignee IS EMPTY
Assigned to specific user
指定用户的问题
assignee = "john.doe"
**By date**:
```jqlassignee = "john.doe"
**按日期**:
```jqlCreated in last 7 days
近7天创建的问题
created >= -7d
created >= -7d
Updated today
今天更新的问题
updated >= startOfDay()
updated >= startOfDay()
Due this week
本周到期的问题
due <= endOfWeek()
**By sprint**:
```jqldue <= endOfWeek()
**按Sprint**:
```jqlCurrent sprint
当前Sprint的问题
sprint in openSprints()
sprint in openSprints()
Specific sprint
指定Sprint的问题
sprint = "Sprint 24"
sprint = "Sprint 24"
Issues not in sprint
未加入Sprint的问题
sprint IS EMPTY
**By label**:
```jqlsprint IS EMPTY
**按标签**:
```jqlHas specific label
带指定标签的问题
labels = backend
labels = backend
Has any of multiple labels
带多个标签中任意一个的问题
labels IN (backend, frontend)
labels IN (backend, frontend)
Missing labels
无标签的问题
labels IS EMPTY
undefinedlabels IS EMPTY
undefinedAdvanced JQL Patterns
高级JQL模式
Combination queries:
jql
undefined组合查询:
jql
undefinedSprint items assigned to me
当前Sprint中我的问题
project = PROJ AND sprint in openSprints() AND assignee = currentUser()
project = PROJ AND sprint in openSprints() AND assignee = currentUser()
High priority bugs
高优先级Bug
project = PROJ AND issuetype = Bug AND priority IN (Highest, High)
project = PROJ AND issuetype = Bug AND priority IN (Highest, High)
Overdue items
逾期问题
duedate < now() AND status != Done
**Using functions**:
```jqlduedate < now() AND status != Done
**使用函数**:
```jqlIssues updated by me
我更新过的问题
updatedBy = currentUser()
updatedBy = currentUser()
Issues where I'm a watcher
我关注的问题
watcher = currentUser()
watcher = currentUser()
Issues in epics
属于史诗的问题
"Epic Link" IS NOT EMPTY
**Ordering results**:
```jql"Epic Link" IS NOT EMPTY
**结果排序**:
```jqlOrder by priority, then created date
按优先级降序、创建日期升序排序
project = PROJ ORDER BY priority DESC, created ASC
project = PROJ ORDER BY priority DESC, created ASC
Multiple sort fields
多字段排序
status = "To Do" ORDER BY priority DESC, updated DESC
For 30+ JQL query examples, see `examples/jql-query-examples.md`.
---status = "To Do" ORDER BY priority DESC, updated DESC
30+个JQL查询示例,请查看`examples/jql-query-examples.md`。
---ADF (Atlassian Document Format)
ADF(Atlassian文档格式)
ADF is Jira's JSON-based format for rich text content in descriptions and comments.
ADF是Jira用于描述和评论中富文本内容的JSON格式。
Basic ADF Structure
基础ADF结构
typescript
// Simple text paragraph
const adf = {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Hello, world!'
}
]
}
]
};typescript
// 简单文本段落
const adf = {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '你好,世界!'
}
]
}
]
};Text Formatting
文本格式化
Bold, italic, code:
typescript
{
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is ',
marks: []
},
{
type: 'text',
text: 'bold',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: ', '
},
{
type: 'text',
text: 'italic',
marks: [{ type: 'em' }]
},
{
type: 'text',
text: ', and '
},
{
type: 'text',
text: 'code',
marks: [{ type: 'code' }]
}
]
}
]
}加粗、斜体、代码:
typescript
{
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '这是 ',
marks: []
},
{
type: 'text',
text: '加粗',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: ','
},
{
type: 'text',
text: '斜体',
marks: [{ type: 'em' }]
},
{
type: 'text',
text: ',和 '
},
{
type: 'text',
text: '代码',
marks: [{ type: 'code' }]
}
]
}
]
}Links
链接
typescript
{
type: 'text',
text: 'Click here',
marks: [
{
type: 'link',
attrs: {
href: 'https://example.com'
}
}
]
}typescript
{
type: 'text',
text: '点击这里',
marks: [
{
type: 'link',
attrs: {
href: 'https://example.com'
}
}
]
}Code Blocks
代码块
typescript
{
type: 'codeBlock',
attrs: {
language: 'typescript'
},
content: [
{
type: 'text',
text: 'function hello() {\n console.log("Hello");\n}'
}
]
}typescript
{
type: 'codeBlock',
attrs: {
language: 'typescript'
},
content: [
{
type: 'text',
text: 'function hello() {\n console.log("Hello");\n}'
}
]
}Lists
列表
Bullet list:
typescript
{
type: 'bulletList',
content: [
{
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'First item'
}
]
}
]
},
{
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Second item'
}
]
}
]
}
]
}Ordered list:
typescript
{
type: 'orderedList',
content: [
// Same listItem structure as bulletList
]
}无序列表:
typescript
{
type: 'bulletList',
content: [
{
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '第一项'
}
]
}
]
},
{
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: '第二项'
}
]
}
]
}
]
}有序列表:
typescript
{
type: 'orderedList',
content: [
// 与无序列表的listItem结构相同
]
}Helper Functions
辅助函数
typescript
// Create simple text paragraph
function createParagraph(text: string) {
return {
type: 'paragraph',
content: [
{
type: 'text',
text
}
]
};
}
// Create ADF document
function createADFDocument(...paragraphs: any[]) {
return {
type: 'doc',
version: 1,
content: paragraphs
};
}
// Usage
const doc = createADFDocument(
createParagraph('First paragraph'),
createParagraph('Second paragraph')
);For complete ADF specification and templates, see and .
references/adf-format-guide.mdexamples/adf-comment-templates.mdtypescript
// 创建简单文本段落
function createParagraph(text: string) {
return {
type: 'paragraph',
content: [
{
type: 'text',
text
}
]
};
}
// 创建ADF文档
function createADFDocument(...paragraphs: any[]) {
return {
type: 'doc',
version: 1,
content: paragraphs
};
}
// 使用示例
const doc = createADFDocument(
createParagraph('第一段'),
createParagraph('第二段')
);完整的ADF规范和模板,请查看和。
references/adf-format-guide.mdexamples/adf-comment-templates.mdIssue Detection and Parsing
问题检测与解析
Jira URL Patterns
Jira URL模式
typescript
// Jira issue URL pattern
const JIRA_ISSUE_URL = /https?:\/\/([^\/]+)\.atlassian\.net\/browse\/([A-Z]+-\d+)/g;
// Custom Jira domain
const JIRA_CUSTOM_URL = /https?:\/\/jira\.([^\/]+)\.com\/browse\/([A-Z]+-\d+)/g;
function detectJiraIssues(text: string) {
const matches = Array.from(text.matchAll(JIRA_ISSUE_URL));
return matches.map(match => ({
url: match[0],
domain: match[1],
key: match[2]
}));
}
// Example
const text = "See https://mycompany.atlassian.net/browse/PROJ-123";
const issues = detectJiraIssues(text);
// => [{ url: "...", domain: "mycompany", key: "PROJ-123" }]typescript
// Jira问题URL模式
const JIRA_ISSUE_URL = /https?:\/\/([^\/]+)\.atlassian\.net\/browse\/([A-Z]+-\d+)/g;
// 自定义Jira域名
const JIRA_CUSTOM_URL = /https?:\/\/jira\.([^\/]+)\.com\/browse\/([A-Z]+-\d+)/g;
function detectJiraIssues(text: string) {
const matches = Array.from(text.matchAll(JIRA_ISSUE_URL));
return matches.map(match => ({
url: match[0],
domain: match[1],
key: match[2]
}));
}
// 示例
const text = "查看 https://mycompany.atlassian.net/browse/PROJ-123";
const issues = detectJiraIssues(text);
// => [{ url: "...", domain: "mycompany", key: "PROJ-123" }]Jira Issue Key Pattern
Jira问题键模式
typescript
// Issue key pattern (e.g., PROJ-123)
const JIRA_KEY = /\b([A-Z]{2,10}-\d+)\b/g;
function extractJiraKeys(text: string): string[] {
const matches = Array.from(text.matchAll(JIRA_KEY));
return matches.map(m => m[1]);
}
// Example
const text = "Implements PROJ-123 and fixes PROJ-456";
const keys = extractJiraKeys(text);
// => ["PROJ-123", "PROJ-456"]typescript
// 问题键模式(例如:PROJ-123)
const JIRA_KEY = /\b([A-Z]{2,10}-\d+)\b/g;
function extractJiraKeys(text: string): string[] {
const matches = Array.from(text.matchAll(JIRA_KEY));
return matches.map(m => m[1]);
}
// 示例
const text = "实现PROJ-123并修复PROJ-456";
const keys = extractJiraKeys(text);
// => ["PROJ-123", "PROJ-456"]Auto-fetch Issue Details
自动获取问题详情
typescript
async function fetchJiraIssue(key: string) {
const response = await jiraClient.get(`/issue/${key}`);
return {
key: response.data.key,
summary: response.data.fields.summary,
status: response.data.fields.status.name,
assignee: response.data.fields.assignee?.displayName,
url: `https://your-domain.atlassian.net/browse/${key}`
};
}
// Auto-enrich text with issue details
async function enrichWithJiraData(text: string) {
const keys = extractJiraKeys(text);
const issues = await Promise.all(keys.map(fetchJiraIssue));
let enriched = text;
issues.forEach(issue => {
const pattern = new RegExp(issue.key, 'g');
enriched = enriched.replace(
pattern,
`[${issue.key}](${issue.url}) (${issue.summary})`
);
});
return enriched;
}typescript
async function fetchJiraIssue(key: string) {
const response = await jiraClient.get(`/issue/${key}`);
return {
key: response.data.key,
summary: response.data.fields.summary,
status: response.data.fields.status.name,
assignee: response.data.fields.assignee?.displayName,
url: `https://your-domain.atlassian.net/browse/${key}`
};
}
// 自动为文本补充问题详情
async function enrichWithJiraData(text: string) {
const keys = extractJiraKeys(text);
const issues = await Promise.all(keys.map(fetchJiraIssue));
let enriched = text;
issues.forEach(issue => {
const pattern = new RegExp(issue.key, 'g');
enriched = enriched.replace(
pattern,
`[${issue.key}](${issue.url}) (${issue.summary})`
);
});
return enriched;
}Sprint Management
Sprint管理
Sprint Planning Workflow
Sprint规划工作流
bash
undefinedbash
undefined1. Create new sprint
1. 创建新Sprint
acli jira --action createSprint
--board "PROJ Board"
--name "Sprint 25"
--startDate "2024-01-15"
--endDate "2024-01-28"
--board "PROJ Board"
--name "Sprint 25"
--startDate "2024-01-15"
--endDate "2024-01-28"
acli jira --action createSprint
--board "PROJ Board"
--name "Sprint 25"
--startDate "2024-01-15"
--endDate "2024-01-28"
--board "PROJ Board"
--name "Sprint 25"
--startDate "2024-01-15"
--endDate "2024-01-28"
2. Add issues to sprint (from JQL query)
2. 将问题添加到Sprint(来自JQL查询)
acli jira --action getIssueList
--jql "project = PROJ AND labels = 'sprint-ready'"
--outputFormat 999 |
acli jira --action addIssuesToSprint
--sprint "Sprint 25"
--issue "@-"
--jql "project = PROJ AND labels = 'sprint-ready'"
--outputFormat 999 |
acli jira --action addIssuesToSprint
--sprint "Sprint 25"
--issue "@-"
acli jira --action getIssueList
--jql "project = PROJ AND labels = 'sprint-ready'"
--outputFormat 999 |
acli jira --action addIssuesToSprint
--sprint "Sprint 25"
--issue "@-"
--jql "project = PROJ AND labels = 'sprint-ready'"
--outputFormat 999 |
acli jira --action addIssuesToSprint
--sprint "Sprint 25"
--issue "@-"
3. Start sprint
3. 启动Sprint
acli jira --action startSprint
--sprint "Sprint 25"
--sprint "Sprint 25"
undefinedacli jira --action startSprint
--sprint "Sprint 25"
--sprint "Sprint 25"
undefinedSprint Reporting
Sprint报告
typescript
interface SprintMetrics {
name: string;
total: number;
completed: number;
inProgress: number;
todo: number;
velocity: number;
}
async function getSprintMetrics(sprintId: string): Promise<SprintMetrics> {
const response = await jiraClient.get(`/sprint/${sprintId}/issues`);
const issues = response.data.issues;
const completed = issues.filter((i: any) => i.fields.status.name === 'Done').length;
const inProgress = issues.filter((i: any) => i.fields.status.name === 'In Progress').length;
const todo = issues.filter((i: any) => i.fields.status.name === 'To Do').length;
return {
name: response.data.sprint.name,
total: issues.length,
completed,
inProgress,
todo,
velocity: (completed / issues.length) * 100
};
}typescript
interface SprintMetrics {
name: string;
total: number;
completed: number;
inProgress: number;
todo: number;
velocity: number;
}
async function getSprintMetrics(sprintId: string): Promise<SprintMetrics> {
const response = await jiraClient.get(`/sprint/${sprintId}/issues`);
const issues = response.data.issues;
const completed = issues.filter((i: any) => i.fields.status.name === 'Done').length;
const inProgress = issues.filter((i: any) => i.fields.status.name === 'In Progress').length;
const todo = issues.filter((i: any) => i.fields.status.name === 'To Do').length;
return {
name: response.data.sprint.name,
total: issues.length,
completed,
inProgress,
todo,
velocity: (completed / issues.length) * 100
};
}Quick Reference
快速参考
Essential acli Commands
核心acli命令
- - Query issues
acli jira --action getIssueList --jql "query" - - Create issue
acli jira --action createIssue --project PROJ --type Story --summary "..." - - Change status
acli jira --action transitionIssue --issue KEY --transition "Status" - - Add to sprint
acli jira --action addIssuesToSprint --sprint "Sprint" --issue "KEY" - - List sprints
acli jira --action getSprintList --board "Board"
- - 查询问题
acli jira --action getIssueList --jql "query" - - 创建问题
acli jira --action createIssue --project PROJ --type Story --summary "..." - - 切换状态
acli jira --action transitionIssue --issue KEY --transition "Status" - - 添加到Sprint
acli jira --action addIssuesToSprint --sprint "Sprint" --issue "KEY" - - 列出Sprint
acli jira --action getSprintList --board "Board"
Key API Endpoints
关键API端点
- - Get issue
GET /issue/{issueKey} - - Create issue
POST /issue - - Update issue
PUT /issue/{issueKey} - - Transition issue
POST /issue/{issueKey}/transitions - - Add comment
POST /issue/{issueKey}/comment
- - 获取问题
GET /issue/{issueKey} - - 创建问题
POST /issue - - 更新问题
PUT /issue/{issueKey} - - 流转状态
POST /issue/{issueKey}/transitions - - 添加评论
POST /issue/{issueKey}/comment
JQL Quick Patterns
JQL快速模式
- - My issues
project = PROJ AND assignee = currentUser() - - Current sprint
sprint in openSprints() - - Prioritized backlog
status = "To Do" ORDER BY priority DESC - - Recent issues
created >= -7d
- - 我的问题
project = PROJ AND assignee = currentUser() - - 当前Sprint
sprint in openSprints() - - 按优先级排序的待办
status = "To Do" ORDER BY priority DESC - - 近期问题
created >= -7d
ADF Basics
ADF基础
- Paragraph:
{type: 'paragraph', content: [{type: 'text', text: '...'}]} - Bold:
marks: [{type: 'strong'}] - Code:
marks: [{type: 'code'}] - Link:
marks: [{type: 'link', attrs: {href: '...'}}]
- 段落:
{type: 'paragraph', content: [{type: 'text', text: '...'}]} - 加粗:
marks: [{type: 'strong'}] - 代码:
marks: [{type: 'code'}] - 链接:
marks: [{type: 'link', attrs: {href: '...'}}]
Best Practices
最佳实践
- Always use API tokens (not passwords) for authentication
- Cache Jira project metadata to reduce API calls
- Use JQL for complex queries instead of filtering in code
- Validate issue keys before API calls (format: PROJECT-123)
- Use ADF for all rich text content (descriptions, comments)
- Handle Jira API rate limits (10 requests/second)
- 始终使用API令牌而非密码进行认证
- 缓存Jira项目元数据以减少API调用
- 复杂查询使用JQL而非在代码中过滤
- API调用前验证问题键格式(格式:PROJECT-123)
- 所有富文本内容(描述、评论)使用ADF
- 处理Jira API速率限制(10请求/秒)
Multi-Specialist ADF Comment Support
多专家ADF评论支持
When working with multi-specialist implementations, Jira comments need to aggregate work from multiple specialists into a single, well-formatted comment.
在多专家协作的实现场景中,Jira评论需要将多位专家的工作汇总为格式规范的单条评论。
Detection Logic
检测逻辑
Check for multi-specialist mode:
bash
undefined检测是否为多专家模式:
bash
undefinedDetect if this is a multi-specialist implementation
检测是否为多专家实现
FEATURE_NAME="authentication" # Extract from issue or context
if [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
echo "Multi-specialist mode detected"
MODE="multi-specialist"
else
echo "Single-specialist mode"
MODE="single-specialist"
fi
**Gather specialist information**:
```bashFEATURE_NAME="authentication" # 从问题或上下文提取
if [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
echo "检测到多专家模式"
MODE="multi-specialist"
else
echo "单专家模式"
MODE="single-specialist"
fi
**收集专家信息**:
```bashList all specialists who worked on this feature
列出所有参与该功能的专家
if [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
specialists=$(ls -d .agency/handoff/${FEATURE_NAME}/*/ | xargs -n1 basename)
for specialist in $specialists; do
echo "Found specialist: $specialist"
# Read specialist's summary
if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md" ]; then
cat ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md"
fi
# Read specialist's verification
if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md" ]; then
cat ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md"
fidone
fi
undefinedif [ -d ".agency/handoff/${FEATURE_NAME}" ]; then
specialists=$(ls -d .agency/handoff/${FEATURE_NAME}/*/ | xargs -n1 basename)
for specialist in $specialists; do
echo "发现专家: $specialist"
# 读取专家的摘要
if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md" ]; then
cat ".agency/handoff/${FEATURE_NAME}/${specialist}/summary.md"
fi
# 读取专家的验证结果
if [ -f ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md" ]; then
cat ".agency/handoff/${FEATURE_NAME}/${specialist}/verification.md"
fidone
fi
undefinedMulti-Specialist ADF Comment Template
多专家ADF评论模板
Complete example with multiple specialists:
typescript
interface SpecialistWork {
name: string;
displayName: string;
summary: string;
filesChanged: string[];
testResults: string;
status: 'success' | 'warning' | 'error';
}
function createMultiSpecialistComment(
featureName: string,
specialists: SpecialistWork[],
overallStatus: 'success' | 'warning' | 'error',
integrationPoints: string[]
): object {
const statusEmoji = {
success: '✅',
warning: '⚠️',
error: '❌'
};
const panelType = {
success: 'success',
warning: 'warning',
error: 'error'
};
return {
version: 1,
type: 'doc',
content: [
// Header panel with overall status
{
type: 'panel',
attrs: {
panelType: panelType[overallStatus]
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[overallStatus]} Multi-Specialist Implementation Complete`,
marks: [{ type: 'strong' }]
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: `Feature: ${featureName} | Specialists: ${specialists.length}`
}
]
}
]
},
// Specialists summary
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: 'Specialist Contributions'
}
]
},
// List of specialists with status
{
type: 'bulletList',
content: specialists.map(specialist => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[specialist.status]} `,
marks: []
},
{
type: 'text',
text: specialist.displayName,
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: ` - ${specialist.summary}`
}
]
}
]
}))
},
// Detailed work by specialist (collapsible-like sections)
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: 'Detailed Work Breakdown'
}
]
},
...specialists.flatMap(specialist => [
// Specialist heading
{
type: 'heading',
attrs: { level: 4 },
content: [
{
type: 'text',
text: `${specialist.displayName} ${statusEmoji[specialist.status]}`
}
]
},
// Summary
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Summary: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: specialist.summary
}
]
},
// Files changed
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Files Changed: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: `${specialist.filesChanged.length} files`
}
]
},
{
type: 'bulletList',
content: specialist.filesChanged.slice(0, 10).map(file => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: file,
marks: [{ type: 'code' }]
}
]
}
]
}))
},
// Test results
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Tests: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: specialist.testResults
}
]
}
]),
// Integration points
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: 'Integration Points'
}
]
},
{
type: 'bulletList',
content: integrationPoints.map(point => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: point
}
]
}
]
}))
}
]
};
}Usage example:
typescript
const specialists: SpecialistWork[] = [
{
name: 'backend-architect',
displayName: 'Backend Architect',
summary: 'Implemented authentication API with JWT and refresh tokens',
filesChanged: [
'src/api/auth/login.ts',
'src/api/auth/refresh.ts',
'src/middleware/authenticate.ts',
'src/models/user.ts'
],
testResults: 'All tests passing (24/24)',
status: 'success'
},
{
name: 'frontend-developer',
displayName: 'Frontend Developer',
summary: 'Created login/signup forms and integrated with auth API',
filesChanged: [
'src/components/LoginForm.tsx',
'src/components/SignupForm.tsx',
'src/hooks/useAuth.ts',
'src/pages/profile.tsx'
],
testResults: 'All tests passing (18/18)',
status: 'success'
}
];
const comment = createMultiSpecialistComment(
'Authentication System',
specialists,
'success',
[
'Backend exposes /api/auth/login and /api/auth/refresh endpoints',
'Frontend uses useAuth hook to manage authentication state',
'JWT tokens stored in httpOnly cookies',
'Protected routes redirect to login when unauthenticated'
]
);
// Post to Jira
await jiraClient.post(`/issue/PROJ-123/comment`, { body: comment });包含多位专家的完整示例:
typescript
interface SpecialistWork {
name: string;
displayName: string;
summary: string;
filesChanged: string[];
testResults: string;
status: 'success' | 'warning' | 'error';
}
function createMultiSpecialistComment(
featureName: string,
specialists: SpecialistWork[],
overallStatus: 'success' | 'warning' | 'error',
integrationPoints: string[]
): object {
const statusEmoji = {
success: '✅',
warning: '⚠️',
error: '❌'
};
const panelType = {
success: 'success',
warning: 'warning',
error: 'error'
};
return {
version: 1,
type: 'doc',
content: [
// 顶部的整体状态面板
{
type: 'panel',
attrs: {
panelType: panelType[overallStatus]
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[overallStatus]} 多专家实现完成`,
marks: [{ type: 'strong' }]
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: `功能: ${featureName} | 专家数量: ${specialists.length}`
}
]
}
]
},
// 专家摘要
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: '专家贡献'
}
]
},
// 带状态表情的专家列表
{
type: 'bulletList',
content: specialists.map(specialist => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[specialist.status]} `,
marks: []
},
{
type: 'text',
text: specialist.displayName,
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: ` - ${specialist.summary}`
}
]
}
]
}))
},
// 按专家拆分的详细工作(类折叠面板)
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: '详细工作分解'
}
]
},
...specialists.flatMap(specialist => [
// 专家标题
{
type: 'heading',
attrs: { level: 4 },
content: [
{
type: 'text',
text: `${specialist.displayName} ${statusEmoji[specialist.status]}`
}
]
},
// 摘要
{
type: 'paragraph',
content: [
{
type: 'text',
text: '摘要: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: specialist.summary
}
]
},
// 修改的文件
{
type: 'paragraph',
content: [
{
type: 'text',
text: '修改的文件: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: `${specialist.filesChanged.length} 个文件`
}
]
},
{
type: 'bulletList',
content: specialist.filesChanged.slice(0, 10).map(file => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: file,
marks: [{ type: 'code' }]
}
]
}
]
}))
},
// 测试结果
{
type: 'paragraph',
content: [
{
type: 'text',
text: '测试结果: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: specialist.testResults
}
]
}
]),
// 集成点
{
type: 'heading',
attrs: { level: 3 },
content: [
{
type: 'text',
text: '集成点'
}
]
},
{
type: 'bulletList',
content: integrationPoints.map(point => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: point
}
]
}
]
}))
}
]
};
}使用示例:
typescript
const specialists: SpecialistWork[] = [
{
name: 'backend-architect',
displayName: '后端架构师',
summary: '实现了带JWT和刷新令牌的认证API',
filesChanged: [
'src/api/auth/login.ts',
'src/api/auth/refresh.ts',
'src/middleware/authenticate.ts',
'src/models/user.ts'
],
testResults: '所有测试通过 (24/24)',
status: 'success'
},
{
name: 'frontend-developer',
displayName: '前端开发工程师',
summary: '创建了登录/注册表单并与认证API集成',
filesChanged: [
'src/components/LoginForm.tsx',
'src/components/SignupForm.tsx',
'src/hooks/useAuth.ts',
'src/pages/profile.tsx'
],
testResults: '所有测试通过 (18/18)',
status: 'success'
}
];
const comment = createMultiSpecialistComment(
'认证系统',
specialists,
'success',
[
'后端暴露/api/auth/login和/api/auth/refresh端点',
'前端使用useAuth钩子管理认证状态',
'JWT令牌存储在httpOnly Cookie中',
'未认证时受保护路由重定向到登录页'
]
);
// 发布到Jira
await jiraClient.post(`/issue/PROJ-123/comment`, { body: comment });Single-Specialist ADF Comment Template
单专家ADF评论模板
Backward compatibility - Keep existing single-specialist format:
typescript
function createSingleSpecialistComment(
summary: string,
filesChanged: string[],
testResults: string,
status: 'success' | 'warning' | 'error'
): object {
const statusEmoji = {
success: '✅',
warning: '⚠️',
error: '❌'
};
const panelType = {
success: 'success',
warning: 'warning',
error: 'error'
};
return {
version: 1,
type: 'doc',
content: [
{
type: 'panel',
attrs: {
panelType: panelType[status]
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[status]} Implementation Complete`,
marks: [{ type: 'strong' }]
}
]
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Summary: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: summary
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Files Changed: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: `${filesChanged.length} files`
}
]
},
{
type: 'bulletList',
content: filesChanged.slice(0, 10).map(file => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: file,
marks: [{ type: 'code' }]
}
]
}
]
}))
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: 'Tests: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: testResults
}
]
}
]
};
}向后兼容 - 保留现有单专家格式:
typescript
function createSingleSpecialistComment(
summary: string,
filesChanged: string[],
testResults: string,
status: 'success' | 'warning' | 'error'
): object {
const statusEmoji = {
success: '✅',
warning: '⚠️',
error: '❌'
};
const panelType = {
success: 'success',
warning: 'warning',
error: 'error'
};
return {
version: 1,
type: 'doc',
content: [
{
type: 'panel',
attrs: {
panelType: panelType[status]
},
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: `${statusEmoji[status]} 实现完成`,
marks: [{ type: 'strong' }]
}
]
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '摘要: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: summary
}
]
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '修改的文件: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: `${filesChanged.length} 个文件`
}
]
},
{
type: 'bulletList',
content: filesChanged.slice(0, 10).map(file => ({
type: 'listItem',
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: file,
marks: [{ type: 'code' }]
}
]
}
]
}))
},
{
type: 'paragraph',
content: [
{
type: 'text',
text: '测试结果: ',
marks: [{ type: 'strong' }]
},
{
type: 'text',
text: testResults
}
]
}
]
};
}Auto-Detection Wrapper
自动检测包装器
Automatically choose the right format:
typescript
async function postImplementationComment(
issueKey: string,
featureName: string
): Promise<void> {
const handoffDir = `.agency/handoff/${featureName}`;
// Check if multi-specialist mode
if (fs.existsSync(handoffDir)) {
// Multi-specialist mode
const specialists: SpecialistWork[] = [];
const specialistDirs = fs.readdirSync(handoffDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
for (const specialistName of specialistDirs) {
const summaryPath = `${handoffDir}/${specialistName}/summary.md`;
const verificationPath = `${handoffDir}/${specialistName}/verification.md`;
if (fs.existsSync(summaryPath)) {
const summary = fs.readFileSync(summaryPath, 'utf-8');
const verification = fs.existsSync(verificationPath)
? fs.readFileSync(verificationPath, 'utf-8')
: '';
// Parse summary and verification to extract data
const specialist = parseSpecialistData(specialistName, summary, verification);
specialists.push(specialist);
}
}
// Determine overall status
const overallStatus = specialists.every(s => s.status === 'success')
? 'success'
: specialists.some(s => s.status === 'error')
? 'error'
: 'warning';
// Extract integration points from summaries
const integrationPoints = extractIntegrationPoints(specialists);
const comment = createMultiSpecialistComment(
featureName,
specialists,
overallStatus,
integrationPoints
);
await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
} else {
// Single-specialist mode (backward compatible)
const summary = 'Implementation completed';
const filesChanged = await getChangedFiles();
const testResults = 'All tests passing';
const status = 'success';
const comment = createSingleSpecialistComment(
summary,
filesChanged,
testResults,
status
);
await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
}
}自动选择合适的格式:
typescript
async function postImplementationComment(
issueKey: string,
featureName: string
): Promise<void> {
const handoffDir = `.agency/handoff/${featureName}`;
// 检查是否为多专家模式
if (fs.existsSync(handoffDir)) {
// 多专家模式
const specialists: SpecialistWork[] = [];
const specialistDirs = fs.readdirSync(handoffDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name);
for (const specialistName of specialistDirs) {
const summaryPath = `${handoffDir}/${specialistName}/summary.md`;
const verificationPath = `${handoffDir}/${specialistName}/verification.md`;
if (fs.existsSync(summaryPath)) {
const summary = fs.readFileSync(summaryPath, 'utf-8');
const verification = fs.existsSync(verificationPath)
? fs.readFileSync(verificationPath, 'utf-8')
: '';
// 解析摘要和验证结果以提取数据
const specialist = parseSpecialistData(specialistName, summary, verification);
specialists.push(specialist);
}
}
// 确定整体状态
const overallStatus = specialists.every(s => s.status === 'success')
? 'success'
: specialists.some(s => s.status === 'error')
? 'error'
: 'warning';
// 从摘要中提取集成点
const integrationPoints = extractIntegrationPoints(specialists);
const comment = createMultiSpecialistComment(
featureName,
specialists,
overallStatus,
integrationPoints
);
await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
} else {
// 单专家模式(向后兼容)
const summary = '实现完成';
const filesChanged = await getChangedFiles();
const testResults = '所有测试通过';
const status = 'success';
const comment = createSingleSpecialistComment(
summary,
filesChanged,
testResults,
status
);
await jiraClient.post(`/issue/${issueKey}/comment`, { body: comment });
}
}Helper Functions
辅助函数
typescript
function parseSpecialistData(
name: string,
summary: string,
verification: string
): SpecialistWork {
// Extract display name
const displayNames: Record<string, string> = {
'backend-architect': 'Backend Architect',
'frontend-developer': 'Frontend Developer',
'database-specialist': 'Database Specialist',
'devops-engineer': 'DevOps Engineer'
};
// Extract summary (first paragraph or heading)
const summaryMatch = summary.match(/^##?\s+(.+)$/m) ||
summary.match(/^(.+)$/m);
const summaryText = summaryMatch ? summaryMatch[1] : 'Work completed';
// Extract files from summary (look for code blocks or lists)
const filesMatch = summary.match(/```[^`]*```/s) ||
summary.match(/^[-*]\s+`([^`]+)`/gm);
const filesChanged = filesMatch
? Array.from(summary.matchAll(/`([^`]+\.[a-z]+)`/g)).map(m => m[1])
: [];
// Extract test results
const testMatch = verification.match(/Tests?:\s*(.+)/i) ||
verification.match(/(\d+\/\d+\s+passing)/i);
const testResults = testMatch ? testMatch[1] : 'Tests completed';
// Determine status from verification
let status: 'success' | 'warning' | 'error' = 'success';
if (verification.includes('❌') || verification.includes('FAIL')) {
status = 'error';
} else if (verification.includes('⚠️') || verification.includes('WARNING')) {
status = 'warning';
}
return {
name,
displayName: displayNames[name] || name,
summary: summaryText,
filesChanged,
testResults,
status
};
}
function extractIntegrationPoints(specialists: SpecialistWork[]): string[] {
const points: string[] = [];
// Look for API endpoints from backend
const backend = specialists.find(s => s.name === 'backend-architect');
if (backend) {
const apiMatches = backend.summary.match(/\/api\/[^\s]+/g);
if (apiMatches) {
points.push(...apiMatches.map(api => `Backend exposes ${api} endpoint`));
}
}
// Look for components from frontend
const frontend = specialists.find(s => s.name === 'frontend-developer');
if (frontend) {
const componentMatches = frontend.filesChanged
.filter(f => f.endsWith('.tsx') || f.endsWith('.jsx'));
if (componentMatches.length > 0) {
points.push(`Frontend components: ${componentMatches.join(', ')}`);
}
}
return points.length > 0 ? points : ['See individual specialist sections for details'];
}
async function getChangedFiles(): Promise<string[]> {
// Get changed files from git using execFile for security
const { execFile } = require('child_process').promises;
try {
const { stdout } = await execFile('git', ['diff', '--name-only', 'HEAD']);
return stdout.trim().split('\n').filter(Boolean);
} catch (error) {
console.error('Failed to get changed files:', error);
return [];
}
}typescript
function parseSpecialistData(
name: string,
summary: string,
verification: string
): SpecialistWork {
// 提取显示名称
const displayNames: Record<string, string> = {
'backend-architect': '后端架构师',
'frontend-developer': '前端开发工程师',
'database-specialist': '数据库专家',
'devops-engineer': 'DevOps工程师'
};
// 提取摘要(第一段或标题)
const summaryMatch = summary.match(/^##?\s+(.+)$/m) ||
summary.match(/^(.+)$/m);
const summaryText = summaryMatch ? summaryMatch[1] : '工作完成';
// 从摘要中提取文件(查找代码块或列表)
const filesMatch = summary.match(/```[^`]*```/s) ||
summary.match(/^[-*]\s+`([^`]+)`/gm);
const filesChanged = filesMatch
? Array.from(summary.matchAll(/`([^`]+\.[a-z]+)`/g)).map(m => m[1])
: [];
// 提取测试结果
const testMatch = verification.match(/Tests?:\s*(.+)/i) ||
verification.match(/(\d+\/\d+\s+passing)/i);
const testResults = testMatch ? testMatch[1] : '测试完成';
// 从验证结果中确定状态
let status: 'success' | 'warning' | 'error' = 'success';
if (verification.includes('❌') || verification.includes('FAIL')) {
status = 'error';
} else if (verification.includes('⚠️') || verification.includes('WARNING')) {
status = 'warning';
}
return {
name,
displayName: displayNames[name] || name,
summary: summaryText,
filesChanged,
testResults,
status
};
}
function extractIntegrationPoints(specialists: SpecialistWork[]): string[] {
const points: string[] = [];
// 从后端专家中查找API端点
const backend = specialists.find(s => s.name === 'backend-architect');
if (backend) {
const apiMatches = backend.summary.match(/\/api\/[^\s]+/g);
if (apiMatches) {
points.push(...apiMatches.map(api => `后端暴露 ${api} 端点`));
}
}
// 从前端专家中查找组件
const frontend = specialists.find(s => s.name === 'frontend-developer');
if (frontend) {
const componentMatches = frontend.filesChanged
.filter(f => f.endsWith('.tsx') || f.endsWith('.jsx'));
if (componentMatches.length > 0) {
points.push(`前端组件: ${componentMatches.join(', ')}`);
}
}
return points.length > 0 ? points : ['请查看各专家部分了解详情'];
}
async function getChangedFiles(): Promise<string[]> {
// 使用execFile从git获取修改的文件以保证安全
const { execFile } = require('child_process').promises;
try {
const { stdout } = await execFile('git', ['diff', '--name-only', 'HEAD']);
return stdout.trim().split('\n').filter(Boolean);
} catch (error) {
console.error('获取修改的文件失败:', error);
return [];
}
}Best Practices
最佳实践
Multi-specialist comments:
- Always include overall status panel at top
- List all specialists with emoji status indicators
- Provide detailed breakdown per specialist
- Highlight integration points between specialists
- Keep file lists reasonable (max 10 per specialist)
- Include test results for each specialist
- Use consistent formatting across all specialists
ADF structure:
- Use panels for status (success/warning/error)
- Use headings (level 3-4) for major sections
- Use bullet lists for file listings
- Use code marks for file paths and code references
- Keep paragraphs focused and concise
- Always validate ADF structure before posting
Detection logic:
- Always check for directory first
.agency/handoff/{feature} - Fall back to single-specialist format if not found
- Handle missing files gracefully (summary.md, verification.md)
- Parse specialist data defensively with defaults
- Validate all parsed data before creating ADF
多专家评论:
- 始终在顶部包含整体状态面板
- 列出所有专家并附带状态表情
- 提供每位专家的详细工作分解
- 突出专家之间的集成点
- 文件列表保持合理长度(每位专家最多10个)
- 包含每位专家的测试结果
- 所有专家使用一致的格式
ADF结构:
- 使用面板显示状态(成功/警告/错误)
- 使用标题(3-4级)划分主要部分
- 使用无序列表展示文件列表
- 使用代码标记显示文件路径和代码引用
- 段落内容聚焦且简洁
- 发布前始终验证ADF结构
检测逻辑:
- 始终优先检查目录
.agency/handoff/{feature} - 未找到则回退到单专家格式
- 优雅处理缺失文件(summary.md、verification.md)
- 带默认值的防御式专家数据解析
- 创建ADF前验证所有解析的数据
Related Skills
相关技能
- jira-adf-generator: Generate properly formatted ADF for Jira comments
- github-integration: Alternative provider integration for comparison
- agency-workflow-patterns: General workflow automation applicable to any provider
- github-workflow-best-practices: Sprint concepts and planning patterns
- jira-adf-generator: 为Jira评论生成格式规范的ADF
- github-integration: 可对比的其他平台集成
- agency-workflow-patterns: 适用于任意平台的通用工作流自动化
- github-workflow-best-practices: Sprint概念和规划模式