Files
Claude-Code-Workflow/.claude/skills/team-planex/roles/planner.md

452 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 create --data '{"title":"..."}' --json` | 从文本创建 issue |
| `ccw issue status <id> --json` | 查看 issue 状态 |
| `ccw issue solution <id> --json` | 查看单个 issue 的 solutions需要 issue ID |
| `ccw issue solutions --status planned --brief` | 批量列出所有已绑定 solutions跨 issue |
| `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
```javascript
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
解析任务描述中的输入类型,确定处理方式。
```javascript
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
// 4) execution-plan.json 输入(来自 req-plan-with-file
let executionPlan = null
// Determine input type
let inputType = 'unknown'
if (issueIds.length > 0) inputType = 'issue_ids'
else if (inputText) inputType = 'text'
else if (planFile) {
// Check if it's an execution-plan.json from req-plan-with-file
try {
const content = JSON.parse(Read(planFile))
if (content.waves && content.issue_ids && content.session_id?.startsWith('RPLAN-')) {
inputType = 'execution_plan'
executionPlan = content
issueIds = content.issue_ids
} else {
inputType = 'plan_file'
}
} catch (e) {
// Not JSON or parse error, fallback to original plan_file parsing
inputType = 'plan_file'
}
} else {
// 任务描述本身可能就是需求文本
inputType = 'text_from_description'
}
```
### Phase 3: Issue Processing Pipeline
根据输入类型执行不同的处理路径:
#### Path A: 文本输入 → 创建 Issue
```javascript
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
```javascript
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 规划。
#### Path D: execution-plan.json → 波次感知处理
```javascript
if (inputType === 'execution_plan') {
const projectRoot = Bash('cd . && pwd').trim()
const waves = executionPlan.waves
let waveNum = 0
for (const wave of waves) {
waveNum++
const waveIssues = wave.issue_ids
// Step 1: issue-plan-agent 生成 solutions
const planResult = Task({
subagent_type: "issue-plan-agent",
run_in_background: false,
description: `Plan solutions for wave ${waveNum}: ${wave.label}`,
prompt: `
issue_ids: ${JSON.stringify(waveIssues)}
project_root: "${projectRoot}"
## Requirements
- Generate solutions for each issue
- Auto-bind single solutions
- Issues come from req-plan decomposition (tags: req-plan)
- Respect inter-issue dependencies: ${JSON.stringify(executionPlan.issue_dependencies)}
`
})
// Step 2: issue-queue-agent 形成 queue
const queueResult = Task({
subagent_type: "issue-queue-agent",
run_in_background: false,
description: `Form queue for wave ${waveNum}: ${wave.label}`,
prompt: `
issue_ids: ${JSON.stringify(waveIssues)}
project_root: "${projectRoot}"
## Requirements
- Order solutions by dependency (DAG)
- Detect conflicts between solutions
- Respect wave dependencies: ${JSON.stringify(wave.depends_on_waves)}
- Output execution queue
`
})
// Step 3: → Phase 4 (Wave Dispatch) - create EXEC-* tasks
// Continue to next wave without waiting for executor
}
// After all waves → Phase 5 (Report + Finalize)
}
```
**关键差异**: 波次分组来自 `executionPlan.waves`,而非固定 batch=5。Progressive 模式下 L0(Wave 1) → L1(Wave 2)Direct 模式下 parallel_group 映射为 wave。
#### Wave 规划Path A/B/C 汇聚)
将 issueIds 按波次分组规划Path D 使用独立的波次逻辑,不走此路径):
```javascript
if (inputType !== 'execution_plan') {
// Path A/B/C: 固定 batch=5 分组
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)
}
} // end if (inputType !== 'execution_plan')
```
### Phase 4: Wave Dispatch
每个 wave 的 queue 完成后,**立即创建 EXEC-\* 任务**供 executor 消费。
```javascript
// 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 规划完毕后,发送最终信号。
```javascript
// 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 列表:
```javascript
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 |
| execution-plan.json parse failure | Fallback to plan_file parsing (Path B) |
| execution-plan.json missing waves | Report error, suggest re-running req-plan |
| Empty input (no issues, no text, no plan) | AskUserQuestion for clarification |
| Wave partially failed | Report partial success, continue with successful issues |