Files
Claude-Code-Workflow/.claude/skills/team-planex/roles/planner.md
catlog22 b2b8688d26 feat: add CLI settings export/import functionality
- Implemented exportSettings and importSettings APIs for CLI settings.
- Added hooks useExportSettings and useImportSettings for managing export/import operations in the frontend.
- Updated SettingsPage to include buttons for exporting and importing CLI settings.
- Enhanced backend to handle export and import requests, including validation and conflict resolution.
- Introduced new data structures for exported settings and import options.
- Updated localization files to support new export/import features.
- Refactored CLI tool configurations to remove hardcoded model defaults, allowing dynamic model retrieval.
2026-02-25 21:40:24 +08:00

481 lines
16 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通过 inline files_touched 冲突检查替代 issue-queue-agent每完成一个 issue 立即派发 EXEC-* 任务。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]` 标识
- 每完成一个 issue 的 solution 后**立即创建 EXEC-\* 任务**并发送 `issue_ready` 信号
- 不等待 executor持续推进下一个 issue
### MUST NOT
- ❌ 直接编写/修改业务代码executor 职责)
- ❌ 调用 code-developer agent
- ❌ 运行项目测试
- ❌ git commit 代码变更
## Message Types
| Type | Direction | Trigger | Description |
|------|-----------|---------|-------------|
| `issue_ready` | planner → executor | 单个 issue solution + EXEC 任务已创建 | 逐 issue 节拍信号 |
| `wave_ready` | planner → executor | 一组 issues 全部派发完毕 | wave 汇总信号 |
| `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 粒度) |
### 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 → 波次感知逐 issue 处理
```javascript
if (inputType === 'execution_plan') {
const projectRoot = Bash('cd . && pwd').trim()
const waves = executionPlan.waves
const dispatchedSolutions = []
// sessionDir 从 planner prompt 中的 sessionDir 变量获取
const execution_method = args.match(/execution_method:\s*(\S+)/)?.[1] || 'Auto'
const code_review = args.match(/code_review:\s*(\S+)/)?.[1] || 'Skip'
let waveNum = 0
for (const wave of waves) {
waveNum++
for (const issueId of wave.issue_ids) {
// Step 1: 单 issue 规划
const planResult = Task({
subagent_type: "issue-plan-agent",
run_in_background: false,
description: `Plan solution for ${issueId}`,
prompt: `issue_ids: ["${issueId}"]
project_root: "${projectRoot}"
## Requirements
- Generate solution for this issue
- Auto-bind single solution
- Issues come from req-plan decomposition (tags: req-plan)
- Respect dependencies: ${JSON.stringify(executionPlan.issue_dependencies)}`
})
// Step 2: 获取 solution + 写中间产物
const solJson = Bash(`ccw issue solution ${issueId} --json`)
const solution = JSON.parse(solJson)
const solutionFile = `${sessionDir}/artifacts/solutions/${issueId}.json`
Write({
file_path: solutionFile,
content: JSON.stringify({
session_id: sessionId, issue_id: issueId, ...solution,
execution_config: { execution_method, code_review },
timestamp: new Date().toISOString()
}, null, 2)
})
// Step 3: inline 冲突检查
const blockedBy = inlineConflictCheck(issueId, solution, dispatchedSolutions)
// Step 4: 创建 EXEC-* 任务
const execTask = TaskCreate({
subject: `EXEC-W${waveNum}-${issueId}: 实现 ${solution.bound?.title || issueId}`,
description: `## 执行任务\n**Wave**: ${waveNum}\n**Issue**: ${issueId}\n**solution_file**: ${solutionFile}\n**execution_method**: ${execution_method}\n**code_review**: ${code_review}`,
activeForm: `实现 ${issueId}`,
owner: "executor"
})
if (blockedBy.length > 0) {
TaskUpdate({ taskId: execTask.id, addBlockedBy: blockedBy })
}
// Step 5: 累积 + 节拍信号
dispatchedSolutions.push({ issueId, solution, execTaskId: execTask.id })
mcp__ccw-tools__team_msg({
operation: "log", team: "planex", from: "planner", to: "executor",
type: "issue_ready",
summary: `[planner] issue_ready: ${issueId}`,
ref: solutionFile
})
SendMessage({
type: "message", recipient: "executor",
content: `## [planner] Issue Ready: ${issueId}\n**solution_file**: ${solutionFile}\n**EXEC task**: ${execTask.subject}`,
summary: `[planner] issue_ready: ${issueId}`
})
}
// wave 级汇总
mcp__ccw-tools__team_msg({
operation: "log", team: "planex", from: "planner", to: "executor",
type: "wave_ready",
summary: `[planner] Wave ${waveNum} fully dispatched: ${wave.issue_ids.length} issues`
})
}
// After all waves → Phase 5 (Report + Finalize)
}
```
**关键差异**: 波次分组来自 `executionPlan.waves`,但每个 issue 独立规划 + 即时派发。Progressive 模式下 L0(Wave 1) → L1(Wave 2)Direct 模式下 parallel_group 映射为 wave。
#### Wave 规划Path A/B/C 汇聚)— 逐 issue 派发
将 issueIds 逐个规划并即时派发Path D 使用独立的波次逻辑,不走此路径):
```javascript
if (inputType !== 'execution_plan') {
const projectRoot = Bash('cd . && pwd').trim()
const dispatchedSolutions = []
const execution_method = args.match(/execution_method:\s*(\S+)/)?.[1] || 'Auto'
const code_review = args.match(/code_review:\s*(\S+)/)?.[1] || 'Skip'
let waveNum = 1 // 简化:不再按 WAVE_SIZE=5 分组,全部视为一个逻辑 wave
for (const issueId of issueIds) {
// Step 1: 单 issue 规划
const planResult = Task({
subagent_type: "issue-plan-agent",
run_in_background: false,
description: `Plan solution for ${issueId}`,
prompt: `issue_ids: ["${issueId}"]
project_root: "${projectRoot}"
## Requirements
- Generate solution for this issue
- Auto-bind single solution
- For multiple solutions, select the most pragmatic one`
})
// Step 2: 获取 solution + 写中间产物
const solJson = Bash(`ccw issue solution ${issueId} --json`)
const solution = JSON.parse(solJson)
const solutionFile = `${sessionDir}/artifacts/solutions/${issueId}.json`
Write({
file_path: solutionFile,
content: JSON.stringify({
session_id: sessionId, issue_id: issueId, ...solution,
execution_config: { execution_method, code_review },
timestamp: new Date().toISOString()
}, null, 2)
})
// Step 3: inline 冲突检查
const blockedBy = inlineConflictCheck(issueId, solution, dispatchedSolutions)
// Step 4: 创建 EXEC-* 任务
const execTask = TaskCreate({
subject: `EXEC-W${waveNum}-${issueId}: 实现 ${solution.bound?.title || issueId}`,
description: `## 执行任务\n**Wave**: ${waveNum}\n**Issue**: ${issueId}\n**solution_file**: ${solutionFile}\n**execution_method**: ${execution_method}\n**code_review**: ${code_review}`,
activeForm: `实现 ${issueId}`,
owner: "executor"
})
if (blockedBy.length > 0) {
TaskUpdate({ taskId: execTask.id, addBlockedBy: blockedBy })
}
// Step 5: 累积 + 节拍信号
dispatchedSolutions.push({ issueId, solution, execTaskId: execTask.id })
mcp__ccw-tools__team_msg({
operation: "log", team: "planex", from: "planner", to: "executor",
type: "issue_ready",
summary: `[planner] issue_ready: ${issueId}`,
ref: solutionFile
})
SendMessage({
type: "message", recipient: "executor",
content: `## [planner] Issue Ready: ${issueId}\n**solution_file**: ${solutionFile}\n**EXEC task**: ${execTask.subject}`,
summary: `[planner] issue_ready: ${issueId}`
})
}
} // end if (inputType !== 'execution_plan')
```
### Phase 4: Inline Conflict Check + Wave Summary
EXEC-* 任务创建已在 Phase 3 逐 issue 完成Phase 4 仅负责 inline 冲突检查函数定义和 wave 汇总。
#### Inline Conflict Check 函数
```javascript
// Inline conflict check — 替代 issue-queue-agent
// 基于 files_touched 重叠检测 + 显式依赖
function inlineConflictCheck(issueId, solution, dispatchedSolutions) {
const currentFiles = solution.bound?.files_touched
|| solution.bound?.affected_files || []
const blockedBy = []
// 1. 文件冲突检测
for (const prev of dispatchedSolutions) {
const prevFiles = prev.solution.bound?.files_touched
|| prev.solution.bound?.affected_files || []
const overlap = currentFiles.filter(f => prevFiles.includes(f))
if (overlap.length > 0) {
blockedBy.push(prev.execTaskId)
}
}
// 2. 显式依赖
const explicitDeps = solution.bound?.dependencies?.on_issues || []
for (const depId of explicitDeps) {
const depTask = dispatchedSolutions.find(d => d.issueId === depId)
if (depTask && !blockedBy.includes(depTask.execTaskId)) {
blockedBy.push(depTask.execTaskId)
}
}
return blockedBy
}
```
#### Wave Summary Signal
Phase 3 循环完成后发送汇总信号Path A/B/C 在全部 issue 完成后Path D 在每个 wave 完成后):
```javascript
// Wave summary — 已在 Phase 3 循环中由每个 wave 末尾发送
// Path A/B/C: 全部 issue 完成后发送一次
mcp__ccw-tools__team_msg({
operation: "log", team: "planex", from: "planner", to: "executor",
type: "wave_ready",
summary: `[planner] Wave ${waveNum} fully dispatched: ${issueIds.length} issues`
})
SendMessage({
type: "message", recipient: "executor",
content: `## [planner] Wave ${waveNum} Complete\n所有 issues 已逐个派发完毕,共 ${dispatchedSolutions.length} 个 EXEC 任务。`,
summary: `[planner] wave_ready: wave ${waveNum}`
})
```
### 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 |
| Inline conflict check failure | Skip conflict detection, create EXEC task without blockedBy |
| 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 |
| Solution artifact write failure | Log warning, create EXEC task without solution_file (executor fallback) |
| Wave partially failed | Report partial success, continue with successful issues |