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

16 KiB
Raw Blame History

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

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

// 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

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 规划。

Path D: execution-plan.json → 波次感知逐 issue 处理

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 使用独立的波次逻辑,不走此路径):

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 函数

// 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 完成后):

// 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 规划完毕后,发送最终信号。

// 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
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