需求管理系统(Jira / Linear)是团队的工作入口,但需求卡片和 Agent 执行之间有一道鸿沟:需求描述模糊、验收标准缺失、字段映射复杂、状态流转不同步。本文把"需求卡片 → Agent 执行计划 → 结果回写"做成一条完整的自动化链路,让 Agent 直接从 Jira / Linear 接活、交付、回写。

Jira / Linear 集成实战:从需求卡片到 Agent 执行计划

需求管理系统(Jira / Linear)是团队的工作入口,但需求卡片和 Agent 执行之间有一道鸿沟:需求描述模糊、验收标准缺失、字段映射复杂、状态流转不同步。本文把"需求卡片 → Agent 执行计划 → 结果回写"做成一条完整的自动化链路,让 Agent 直接从 Jira / Linear 接活、交付、回写。

一、集成的价值与难点

维度 手动流程 集成后
任务创建 PM 写 Issue → 手动拆任务 → 分配给开发者 PM 写 Issue → 自动转 Agent 任务
执行状态 开发者手动更新状态 Agent 自动回写状态
验收 人工对照验收标准逐条检查 Agent 自动执行验收命令并回写结果
沟通成本 开发者在 Jira 和 IDE 之间反复切换 所有信息在 Jira 评论中汇总

难点在于:

  • 字段差异:Jira 和 Linear 的字段模型完全不同
  • 验收标准:多数需求卡片没有可执行的验收条件
  • 状态映射:Jira 的 To Do → In Progress → Done 和 Agent 的 pending → running → completed → review 不是一一对应
  • 权限隔离:Agent 不应该有 Jira 管理员权限

二、集成架构

text
┌──────────────┐                    ┌──────────────┐
│    Jira       │                    │    Linear     │
│  ┌─────────┐ │                    │ ┌─────────┐  │
│  │  Issue   │ │                    │ │  Issue   │  │
│  │  字段/状态│ │                    │ │  字段/状态│  │
│  └────┬────┘ │                    │ └────┬────┘  │
└───────┼──────┘                    └──────┼───────┘
        │                                   │
   Webhook / API                      Webhook / API
        │                                   │
┌───────▼───────────────────────────────────▼───────┐
│              需求适配层 (Issue Adapter)              │
│  ┌──────────────────────────────────────────────┐  │
│  │  字段映射:Jira/Linear 字段 → 统一任务模型    │  │
│  │  状态映射:外部状态 ↔ Agent 内部状态          │  │
│  │  验收提取:从 description 提取可执行验收条件  │  │
│  └──────────────────┬───────────────────────────┘  │
└─────────────────────┼──────────────────────────────┘
                      │
┌─────────────────────▼──────────────────────────────┐
│              Agent 工作台                            │
│  ┌─────────┐  ┌──────────┐  ┌──────────────────┐   │
│  │任务队列  │→│上下文包生成│→│ Agent 执行        │   │
│  └─────────┘  └──────────┘  └────────┬─────────┘   │
│                                       │              │
│  ┌────────────────────────────────────▼──────────┐  │
│  │  结果回写层                                    │  │
│  │  · 状态同步(In Progress → Done)             │  │
│  │  · 评论写入(执行日志 + PR 链接)              │  │
│  │  · 附件上传(测试报告、截图)                  │  │
│  │  · 验收结果回写                               │  │
│  └──────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────┘

三、统一任务模型

3.1 字段映射配置

yaml
# issue-adapter-config.yaml
# Jira 字段映射
jira:
  base_url: "https://your-company.atlassian.net"
  project_key: "ENG"
  
  field_mapping:
    title: "summary"
    description: "description"
    priority: "priority.name"
    assignee: "assignee.emailAddress"
    labels: "labels"
    
    # 自定义字段
    acceptance_criteria: "customfield_10042"   # 验收标准(文本)
    story_points: "story_points"
    sprint: "sprint.name"
    
    # 状态映射
    status_mapping:
      "To Do": "pending"
      "In Progress": "running"
      "In Review": "review"
      "Done": "completed"
      "Blocked": "blocked"
    
    # 反向映射(Agent → Jira)
    reverse_status_mapping:
      "pending": "To Do"
      "queued": "To Do"
      "running": "In Progress"
      "completed": "In Review"    # Agent 完成后进入 Review
      "review": "In Review"
      "failed": "Blocked"
      "approved": "Done"
  
  # 自动触发的标签
  agent_labels:
    trigger: "agent-task"         # 有此标签的 Issue 自动进入 Agent 队列
    in_progress: "agent-running"
    needs_review: "agent-review"
    completed: "agent-done"

# Linear 字段映射
linear:
  field_mapping:
    title: "title"
    description: "description"
    priority: "priority"          # 0-4 数字
    assignee: "assignee.email"
    labels: "labels.name"
    
    status_mapping:
      "Backlog": "pending"
      "Todo": "pending"
      "In Progress": "running"
      "In Review": "review"
      "Done": "completed"
      "Canceled": "cancelled"
    
    reverse_status_mapping:
      "pending": "Todo"
      "running": "In Progress"
      "completed": "Done"
      "review": "In Review"
      "failed": "Blocked"

3.2 统一任务模型

python
# app/models/unified_task.py
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class TaskStatus(Enum):
    PENDING = "pending"
    QUEUED = "queued"
    RUNNING = "running"
    REVIEW = "review"
    COMPLETED = "completed"
    FAILED = "failed"
    APPROVED = "approved"
    CANCELLED = "cancelled"

@dataclass
class AcceptanceCriterion:
    """单条验收标准"""
    description: str          # 人类可读描述
    command: Optional[str]    # 可执行的验收命令
    expected_result: str      # 预期结果
    verified: bool = False
    actual_result: str = ""

@dataclass
class UnifiedTask:
    """统一任务模型——Jira 和 Linear 都转成这个格式"""
    # 来源信息
    source_platform: str           # "jira" | "linear"
    source_id: str                 # Jira Issue Key 或 Linear Issue ID
    source_url: str                # 原始链接
    
    # 基本信息
    title: str
    description: str
    priority: str                  # "P0" | "P1" | "P2" | "P3"
    labels: list[str]
    
    # 人员
    creator: str
    assignee: Optional[str]
    
    # 验收标准
    acceptance_criteria: list[AcceptanceCriterion] = field(default_factory=list)
    
    # Agent 执行信息
    status: TaskStatus = TaskStatus.PENDING
    agent_context: dict = field(default_factory=dict)
    execution_log: list[str] = field(default_factory=list)
    result: Optional[dict] = None
    
    # 时间
    created_at: str = ""
    started_at: Optional[str] = None
    completed_at: Optional[str] = None

3.3 验收标准提取

python
# app/adapters/acceptance_extractor.py
"""
从需求描述中提取可执行的验收标准。
多数需求卡片的验收标准写在描述中,格式不统一,需要 Agent 解析。
"""
import re

def extract_acceptance_criteria(description: str, platform: str = "jira") -> list:
    """
    从描述中提取验收标准。
    支持多种常见格式:
    - Jira 的 - [ ] 复选框格式
    - Given/When/Then BDD 格式
    - 编号列表格式
    """
    criteria = []
    
    # 1. 复选框格式:- [ ] xxx
    checkboxes = re.findall(r"-\s*\[\s*\]\s*(.+)", description)
    for cb in checkboxes:
        criteria.append({
            "description": cb.strip(),
            "command": None,
            "expected_result": cb.strip(),
        })
    
    # 2. Given/When/Then 格式
    bdd_blocks = re.findall(
        r"Given\s+(.+?)\s+When\s+(.+?)\s+Then\s+(.+?)(?=\n\n|\Z|Given)",
        description,
        re.DOTALL
    )
    for given, when, then in bdd_blocks:
        criteria.append({
            "description": f"Given {given.strip()} When {when.strip()} Then {then.strip()}",
            "command": None,
            "expected_result": then.strip(),
        })
    
    # 3. 编号列表:1. xxx
    if not criteria:
        numbered = re.findall(r"^\d+\.\s+(.+)$", description, re.MULTILINE)
        for item in numbered:
            criteria.append({
                "description": item.strip(),
                "command": None,
                "expected_result": item.strip(),
            })
    
    return criteria

def agent_enhance_criteria(criteria: list, context: dict) -> list:
    """
    用 Agent 把模糊的验收标准转成可执行命令。
    例如:"用户能正常登录" → "POST /api/login 返回 200 + token"
    """
    enhanced = []
    for c in criteria:
        if c.get("command"):
            enhanced.append(c)  # 已经有可执行命令
            continue
        
        # 调用 Agent 生成可执行验收命令
        prompt = f"""
        把以下验收标准转成可执行的验收命令。
        
        验收标准:{c['description']}
        项目技术栈:{context.get('tech_stack', 'unknown')}
        测试框架:{context.get('test_framework', 'pytest')}
        
        输出 JSON:
        {{
            "command": "具体的验收命令",
            "expected_result": "预期的输出或退出码"
        }}
        """
        
        # agent_result = call_agent(prompt)
        # c["command"] = agent_result["command"]
        # c["expected_result"] = agent_result["expected_result"]
        enhanced.append(c)
    
    return enhanced

四、结果回写

4.1 状态同步

python
# app/sync/status_sync.py
"""
Agent 任务状态变化时,同步更新 Jira / Linear。
"""

async def sync_status(task: UnifiedTask, new_status: str):
    """同步任务状态到来源平台"""
    
    if task.source_platform == "jira":
        # Jira 状态转换需要先获取 transition ID
        transitions = await jira_client.get_transitions(task.source_id)
        target_transition = None
        
        target_jira_status = JIRA_REVERSE_STATUS_MAP.get(new_status)
        for t in transitions:
            if t["to"]["name"] == target_jira_status:
                target_transition = t["id"]
                break
        
        if target_transition:
            await jira_client.transition_issue(task.source_id, target_transition)
        else:
            log.warning(f"No transition found for {new_status}{target_jira_status}")
    
    elif task.source_platform == "linear":
        target_state = LINEAR_REVERSE_STATUS_MAP.get(new_status)
        if target_state:
            await linear_client.update_issue(
                task.source_id,
                state_id=target_state,
            )

async def add_comment(task: UnifiedTask, comment: str):
    """在来源平台添加评论"""
    
    formatted = f"""🤖 **Agent 工作台更新**

{comment}

---
任务 ID: `{task.source_id}` | [查看原始需求]({task.source_url})
"""
    
    if task.source_platform == "jira":
        await jira_client.add_comment(task.source_id, formatted)
    elif task.source_platform == "linear":
        await linear_client.add_comment(task.source_id, formatted)

4.2 验收结果回写

python
# app/sync/verification_sync.py
"""
验收完成后,把结果回写到需求卡片。
"""

async def sync_verification_results(task: UnifiedTask, results: list):
    """回写验收结果"""
    
    # 生成 Markdown 表格
    table = "| # | 验收标准 | 状态 | 实际结果 |\n"
    table += "|---|---------|------|----------|\n"
    
    all_passed = True
    for i, r in enumerate(results, 1):
        status_icon = "✅" if r["passed"] else "❌"
        if not r["passed"]:
            all_passed = False
        table += f"| {i} | {r['description']} | {status_icon} | {r['actual'][:100]} |\n"
    
    # 生成评论
    summary = "## 验收结果\n\n"
    summary += f"**总结**:{'全部通过 ✅' if all_passed else '部分未通过 ❌'}\n\n"
    summary += table
    summary += f"\n**执行时间**:{datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
    
    if all_passed:
        summary += "\n所有验收标准已通过,任务可以关闭。\n"
    else:
        failed = [r for r in results if not r["passed"]]
        summary += f"\n{len(failed)} 项未通过,需要人工介入。\n"
    
    await add_comment(task, summary)
    
    # 如果全部通过,更新状态为完成
    if all_passed:
        await sync_status(task, "completed")

五、真实经验与踩坑

5.1 Jira 的自定义字段是最大陷阱

场景:验收标准字段在 Jira 中是自定义字段,字段 ID 是 customfield_10042问题:不同 Jira 实例的自定义字段 ID 不同。在一个客户那里是 customfield_10042,在另一个是 customfield_10087。部署到新客户时验收标准全丢了。 解决方案:用字段名而不是 ID 做配置。在初始化时调用 Jira 的 /rest/api/3/field 接口,动态建立 name → id 的映射。配置文件里只写字段名:acceptance_criteria: "验收标准"

5.2 状态同步要防止死循环

场景:Agent 更新 Jira 状态触发 Webhook,Webhook 又触发 Agent 处理状态变化事件,Agent 又更新 Jira…… 问题:死循环,API 被限流。 解决方案:在状态更新时加一个 updated_by: "agent" 标记。Webhook handler 收到事件时检查这个标记,如果是 Agent 自己更新的就跳过。同时限制同一 Issue 的 Webhook 处理频率(1 分钟内同事件只处理 1 次)。

5.3 Linear 的 Webhook 比 Jira 简洁得多

场景:同时集成 Jira 和 Linear,以为工作量差不多。 问题:Jira 的 Webhook 配置需要在管理后台手动操作,字段 API 返回几百个字段(大量自定义),状态转换需要查 transition ID。Linear 用 GraphQL API,一个 webhook 注册搞定,字段模型清晰,状态直接通过 ID 更新。 解决方案:集成层做了适配抽象,但内部实现明显 Linear 更简单。建议新项目优先用 Linear,老系统从 Jira 迁移时注意字段映射的兼容性测试。

六、参数说明表

参数 类型 默认值 说明
platform string 必填 "jira""linear"
base_url string 必填 Jira 实例 URL 或 Linear API URL
api_token string 必填 API Token(加密存储)
project_key string Jira 项目 Key(Linear 不需要)
field_mapping object 见 3.1 字段映射配置
status_mapping object 见 3.1 状态映射配置
agent_trigger_label string "agent-task" 触发 Agent 的标签
auto_sync_status bool true 是否自动同步状态
sync_verification bool true 是否回写验收结果
comment_template string 内置 评论模板
webhook_dedup_window int 60 Webhook 去重窗口(秒)

七、落地检查清单

  • Jira/Linear 的字段映射配置正确(包含自定义字段)
  • 状态映射双向配置一致(外部 → Agent,Agent → 外部)
  • 验收标准能从描述中自动提取
  • Agent 更新状态后不会触发死循环
  • Webhook 有幂等去重机制
  • API Token 加密存储,不硬编码
  • 结果评论格式清晰,包含验收结果表格
  • 全部验收通过时自动更新状态为完成
  • 失败时通知需求创建者
  • 审计日志记录每次状态同步

八、系列导航

上一篇:GitHub 集成实战:Issue、PR、Checks 与 Agent 任务流 下一篇:Lab 006:同一需求从 PRD 到代码,哪个 Agent 最会问问题?