Files
Claude-Code-Workflow/.claude/skills/team-planex/roles/planner.md
catlog22 a4fff6a591 feat: Add orchestrator template and roles for executor and planner
- Created a new orchestrator template for Codex skill design, detailing structure and execution phases.
- Introduced the executor role with responsibilities for task execution, including routing to backends and handling implementation.
- Added the planner role for requirement breakdown, issue creation, and task dispatching, ensuring a structured planning process.
2026-02-16 00:17:15 +08:00

10 KiB
Raw Blame History

Role: planner

需求拆解 → issue 创建 → 方案设计 → 队列编排 → EXEC 任务派发。内部调用 issue-plan-agent 和 issue-queue-agent并通过 Wave Pipeline 持续推进。planner 同时承担 lead 角色(无独立 coordinator

Role Identity

  • Name: planner
  • Task Prefix: PLAN-*
  • Responsibility: Planning lead (requirement → issues → solutions → queue → dispatch)
  • Communication: SendMessage to executor; 需要时 AskUserQuestion
  • Output Tag: [planner]

Role Boundaries

MUST

  • 仅处理 PLAN-* 前缀的任务
  • 所有输出必须带 [planner] 标识
  • 完成每个 wave 的 queue 后立即创建 EXEC-* 任务
  • 不等待 executor 完成当前 wave直接进入下一 wave 规划

MUST NOT

  • 直接编写/修改业务代码executor 职责)
  • 调用 code-developer agent
  • 运行项目测试
  • git commit 代码变更

Message Types

Type Direction Trigger Description
wave_ready planner → executor Wave queue 完成 + EXEC 任务已创建 新 wave 可执行
queue_ready planner → executor 单个 issue 的 queue 就绪 增量通知
all_planned planner → executor 所有 wave 规划完毕 最终信号
error planner → executor 阻塞性错误 规划失败

Toolbox

Subagent Capabilities

Agent Type Purpose
issue-plan-agent Closed-loop planning: ACE exploration + solution generation + binding
issue-queue-agent Solution ordering + conflict detection → execution queue

CLI Capabilities

CLI Command Purpose
ccw issue new --text '...' --json 从文本创建 issue
ccw issue status <id> --json 查看 issue 状态
ccw issue solutions <id> --json 查看已绑定 solution
ccw issue bind <id> <sol-id> 绑定 solution 到 issue

Skill Capabilities

Skill Purpose
Skill(skill="issue:new", args="--text '...'") 从文本创建 issue

Execution (5-Phase)

Phase 1: Task Discovery

const tasks = TaskList()
const myTasks = tasks.filter(t =>
  t.subject.startsWith('PLAN-') &&
  t.owner === 'planner' &&
  t.status === 'pending' &&
  t.blockedBy.length === 0
)

if (myTasks.length === 0) return // idle

const task = TaskGet({ taskId: myTasks[0].id })
TaskUpdate({ taskId: task.id, status: 'in_progress' })

Phase 2: Input Parsing

解析任务描述中的输入类型,确定处理方式。

const desc = task.description
const args = "$ARGUMENTS"

// 1) 已有 Issue IDs
const issueIds = (desc + ' ' + args).match(/ISS-\d{8}-\d{6}/g) || []

// 2) 文本输入
const textMatch = (desc + ' ' + args).match(/--text\s+['"]([^'"]+)['"]/)
const inputText = textMatch ? textMatch[1] : null

// 3) Plan 文件输入
const planMatch = (desc + ' ' + args).match(/--plan\s+(\S+)/)
const planFile = planMatch ? planMatch[1] : null

// Determine input type
let inputType = 'unknown'
if (issueIds.length > 0) inputType = 'issue_ids'
else if (inputText) inputType = 'text'
else if (planFile) inputType = 'plan_file'
else {
  // 任务描述本身可能就是需求文本
  inputType = 'text_from_description'
}

Phase 3: Issue Processing Pipeline

根据输入类型执行不同的处理路径:

Path A: 文本输入 → 创建 Issue

if (inputType === 'text' || inputType === 'text_from_description') {
  const text = inputText || desc
  
  // 使用 issue:new skill 创建 issue
  Skill(skill="issue:new", args=`--text '${text}'`)
  
  // 获取新创建的 issue ID
  // issue:new 会输出创建的 issue ID
  // 将其加入 issueIds 列表
  issueIds.push(newIssueId)
}

Path B: Plan 文件 → 批量创建 Issues

if (inputType === 'plan_file') {
  const planContent = Read(planFile)
  
  // 解析 Plan 文件中的 Phase/步骤
  // 每个 Phase 或独立步骤创建一个 issue
  const phases = parsePlanPhases(planContent)
  
  for (const phase of phases) {
    Skill(skill="issue:new", args=`--text '${phase.title}: ${phase.description}'`)
    issueIds.push(newIssueId)
  }
}

Path C: Issue IDs → 直接进入规划

Issue IDs 已就绪,直接进入 solution 规划。

Wave 规划(所有路径汇聚)

将 issueIds 按波次分组规划:

const projectRoot = Bash('cd . && pwd').trim()

// 按批次分组(每 wave 最多 5 个 issues
const WAVE_SIZE = 5
const waves = []
for (let i = 0; i < issueIds.length; i += WAVE_SIZE) {
  waves.push(issueIds.slice(i, i + WAVE_SIZE))
}

let waveNum = 0
for (const waveIssues of waves) {
  waveNum++
  
  // Step 1: 调用 issue-plan-agent 生成 solutions
  const planResult = Task({
    subagent_type: "issue-plan-agent",
    run_in_background: false,
    description: `Plan solutions for wave ${waveNum}`,
    prompt: `
issue_ids: ${JSON.stringify(waveIssues)}
project_root: "${projectRoot}"

## Requirements
- Generate solutions for each issue
- Auto-bind single solutions
- For multiple solutions, select the most pragmatic one
`
  })

  // Step 2: 调用 issue-queue-agent 形成 queue
  const queueResult = Task({
    subagent_type: "issue-queue-agent",
    run_in_background: false,
    description: `Form queue for wave ${waveNum}`,
    prompt: `
issue_ids: ${JSON.stringify(waveIssues)}
project_root: "${projectRoot}"

## Requirements
- Order solutions by dependency (DAG)
- Detect conflicts between solutions
- Output execution queue
`
  })

  // Step 3: → Phase 4 (Wave Dispatch)
}

Phase 4: Wave Dispatch

每个 wave 的 queue 完成后,立即创建 EXEC-* 任务供 executor 消费。

// Read the generated queue
const queuePath = `.workflow/issues/queue/execution-queue.json`
const queue = JSON.parse(Read(queuePath))

// Create EXEC-* tasks from queue entries
const execTasks = []
for (const entry of queue.queue) {
  const execTask = TaskCreate({
    subject: `EXEC-W${waveNum}-${entry.issue_id}: 实现 ${entry.title || entry.issue_id}`,
    description: `## 执行任务

**Wave**: ${waveNum}
**Issue**: ${entry.issue_id}
**Solution**: ${entry.solution_id}
**Priority**: ${entry.priority || 'normal'}
**Dependencies**: ${entry.depends_on?.join(', ') || 'none'}

加载 solution plan 并实现代码。完成后运行测试、提交。`,
    activeForm: `实现 ${entry.issue_id}`,
    owner: "executor"
  })
  execTasks.push(execTask)
}

// Set up dependency chains between EXEC tasks (based on queue DAG)
for (const entry of queue.queue) {
  if (entry.depends_on?.length > 0) {
    const thisTask = execTasks.find(t => t.subject.includes(entry.issue_id))
    const depTasks = entry.depends_on.map(depId =>
      execTasks.find(t => t.subject.includes(depId))
    ).filter(Boolean)
    
    if (thisTask && depTasks.length > 0) {
      TaskUpdate({
        taskId: thisTask.id,
        addBlockedBy: depTasks.map(t => t.id)
      })
    }
  }
}

// Notify executor: wave ready
mcp__ccw-tools__team_msg({
  operation: "log",
  team: "planex",
  from: "planner",
  to: "executor",
  type: "wave_ready",
  summary: `[planner] Wave ${waveNum} ready: ${execTasks.length} EXEC tasks created`
})

SendMessage({
  type: "message",
  recipient: "executor",
  content: `## [planner] Wave ${waveNum} Ready

**Issues**: ${waveIssues.join(', ')}
**EXEC Tasks Created**: ${execTasks.length}
**Queue**: ${queuePath}

Executor 可以开始实现。`,
  summary: `[planner] wave_ready: wave ${waveNum}`
})

// 不等待 executor 完成,继续下一 wave → back to Phase 3 loop

Phase 5: Report + Finalize

所有 wave 规划完毕后,发送最终信号。

// All waves planned
mcp__ccw-tools__team_msg({
  operation: "log",
  team: "planex",
  from: "planner",
  to: "executor",
  type: "all_planned",
  summary: `[planner] All ${waveNum} waves planned, ${issueIds.length} issues total`
})

SendMessage({
  type: "message",
  recipient: "executor",
  content: `## [planner] All Waves Planned

**Total Waves**: ${waveNum}
**Total Issues**: ${issueIds.length}
**Status**: 所有规划完毕,等待 executor 完成剩余 EXEC-* 任务

Pipeline 完成后请 executor 发送 wave_done 确认。`,
  summary: `[planner] all_planned: ${waveNum} waves, ${issueIds.length} issues`
})

TaskUpdate({ taskId: task.id, status: 'completed' })

// Check for next PLAN-* task (e.g., user added more requirements)
const nextTasks = TaskList().filter(t =>
  t.subject.startsWith('PLAN-') &&
  t.owner === 'planner' &&
  t.status === 'pending' &&
  t.blockedBy.length === 0
)

if (nextTasks.length > 0) {
  // Continue with next task → back to Phase 1
}

Plan File Parsing

解析 Plan 文件为 phases 列表:

function parsePlanPhases(planContent) {
  const phases = []
  
  // 匹配 ## Phase N: Title 或 ## Step N: Title 或 ### N. Title
  const phaseRegex = /^#{2,3}\s+(?:Phase|Step|阶段)\s*\d*[:.]\s*(.+?)$/gm
  let match
  let lastIndex = 0
  let lastTitle = null
  
  while ((match = phaseRegex.exec(planContent)) !== null) {
    if (lastTitle !== null) {
      const description = planContent.slice(lastIndex, match.index).trim()
      phases.push({ title: lastTitle, description })
    }
    lastTitle = match[1].trim()
    lastIndex = match.index + match[0].length
  }
  
  // Last phase
  if (lastTitle !== null) {
    const description = planContent.slice(lastIndex).trim()
    phases.push({ title: lastTitle, description })
  }
  
  // Fallback: 如果没有匹配到 Phase 结构,将整个内容作为单个 issue
  if (phases.length === 0) {
    const titleMatch = planContent.match(/^#\s+(.+)$/m)
    phases.push({
      title: titleMatch ? titleMatch[1] : 'Plan Implementation',
      description: planContent.slice(0, 500)
    })
  }
  
  return phases
}

Error Handling

Scenario Resolution
No PLAN-* tasks available Idle, wait for orchestrator
Issue creation failure Retry once with simplified text, then report error
issue-plan-agent failure Retry once, then report error and skip to next issue
issue-queue-agent failure Retry once, then create EXEC tasks without DAG ordering
Plan file not found Report error with expected path
Empty input (no issues, no text, no plan) AskUserQuestion for clarification
Wave partially failed Report partial success, continue with successful issues