From 1efe2f469ef7e93cef14b1b3cc7dee6faa07c311 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Mon, 23 Feb 2026 22:42:53 +0800 Subject: [PATCH] fix(team-skills): enable true parallel execution with --agent-name mechanism Previously, parallel tasks assigned to the same role (e.g., multiple EXPLORE-* tasks with owner: 'explorer') executed serially because only one agent instance existed per role name. This adds conditional parallel agent spawning with instance-specific names (explorer-1, explorer-2) and --agent-name arg for role task discovery filtering. Affected skills: team-ultra-analyze, team-quality-assurance, team-brainstorm, team-issue. Single-task modes preserve backward compatibility with original agent names. --- .claude/skills/team-brainstorm/SKILL.md | 55 +++++++-- .../team-brainstorm/roles/coordinator.md | 4 +- .../skills/team-brainstorm/roles/ideator.md | 8 +- .claude/skills/team-issue/SKILL.md | 105 ++++++++++++++--- .../skills/team-issue/roles/coordinator.md | 14 ++- .claude/skills/team-issue/roles/explorer.md | 8 +- .../skills/team-issue/roles/implementer.md | 8 +- .../skills/team-quality-assurance/SKILL.md | 91 +++++++++++--- .../roles/coordinator/commands/dispatch.md | 8 +- .../roles/executor/role.md | 8 +- .../roles/generator/role.md | 8 +- .claude/skills/team-ultra-analyze/SKILL.md | 111 +++++++++++++++--- .../team-ultra-analyze/roles/analyst/role.md | 8 +- .../roles/coordinator/commands/dispatch.md | 11 +- .../roles/coordinator/role.md | 5 +- .../team-ultra-analyze/roles/explorer/role.md | 8 +- 16 files changed, 373 insertions(+), 87 deletions(-) diff --git a/.claude/skills/team-brainstorm/SKILL.md b/.claude/skills/team-brainstorm/SKILL.md index 7ddd0ded..47e95f7d 100644 --- a/.claude/skills/team-brainstorm/SKILL.md +++ b/.claude/skills/team-brainstorm/SKILL.md @@ -39,6 +39,7 @@ if (!roleMatch) { const role = roleMatch[1] const teamName = args.match(/--team[=\s]+([\w-]+)/)?.[1] || "brainstorm" +const agentName = args.match(/--agent-name[=\s]+([\w-]+)/)?.[1] || role ``` ### Role Dispatch @@ -119,7 +120,6 @@ mcp__ccw-tools__team_msg({ const TEAM_CONFIG = { name: "brainstorm", sessionDir: ".workflow/.team/BRS-{slug}-{date}/", - msgDir: ".workflow/.team-msg/brainstorm/", sharedMemory: "shared-memory.json" } ``` @@ -182,7 +182,7 @@ Bash(`ccw team log --team "${teamName}" --from "${role}" --to "coordinator" --ty const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith(`${VALID_ROLES[role].prefix}-`) && - t.owner === role && + t.owner === agentName && // Use agentName (e.g., 'ideator-1') instead of role t.status === 'pending' && t.blockedBy.length === 0 ) @@ -245,12 +245,48 @@ IDEA → CHALLENGE → (if critique.severity >= HIGH) → IDEA-fix → CHALLENGE ```javascript TeamCreate({ team_name: teamName }) -// Ideator -Task({ - subagent_type: "general-purpose", - team_name: teamName, - name: "ideator", - prompt: `你是 team "${teamName}" 的 IDEATOR。 +// Ideator — conditional parallel spawn for Full pipeline (multiple angles) +const isFullPipeline = selectedPipeline === 'full' +const ideaAngles = selectedAngles || [] + +if (isFullPipeline && ideaAngles.length > 1) { + // Full pipeline: spawn N ideators for N parallel angle tasks + for (let i = 0; i < ideaAngles.length; i++) { + const agentName = `ideator-${i + 1}` + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: agentName, + prompt: `你是 team "${teamName}" 的 IDEATOR (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 IDEA-* 任务时,调用 Skill(skill="team-brainstorm", args="--role=ideator --agent-name=${agentName}") 执行。 +当前话题: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 IDEA-* 前缀任务 +- 所有输出(SendMessage、team_msg)必须带 [ideator] 标识前缀 +- 仅与 coordinator 通信,不得直接联系其他 worker +- 不得使用 TaskCreate 为其他角色创建任务 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 IDEA-* 任务 +2. Skill(skill="team-brainstorm", args="--role=ideator --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage 结果给 coordinator(带 [ideator] 标识) +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + // Quick/Deep pipeline: single ideator + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "ideator", + prompt: `你是 team "${teamName}" 的 IDEATOR。 当你收到 IDEA-* 任务时,调用 Skill(skill="team-brainstorm", args="--role=ideator") 执行。 当前话题: ${taskDescription} 约束: ${constraints} @@ -269,7 +305,8 @@ Task({ 2. Skill(skill="team-brainstorm", args="--role=ideator") 执行 3. team_msg log + SendMessage 结果给 coordinator(带 [ideator] 标识) 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} // Challenger Task({ diff --git a/.claude/skills/team-brainstorm/roles/coordinator.md b/.claude/skills/team-brainstorm/roles/coordinator.md index 80471518..36ed51dd 100644 --- a/.claude/skills/team-brainstorm/roles/coordinator.md +++ b/.claude/skills/team-brainstorm/roles/coordinator.md @@ -185,10 +185,12 @@ TaskUpdate({ taskId: evalId, owner: "evaluator", addBlockedBy: [synthId] }) ```javascript // 并行创意: IDEA-001 + IDEA-002 + IDEA-003 (no dependencies between them) +// Each gets a distinct agent owner for true parallel execution const ideaAngles = selectedAngles.slice(0, 3) ideaAngles.forEach((angle, i) => { + const ideatorName = ideaAngles.length > 1 ? `ideator-${i+1}` : 'ideator' TaskCreate({ subject: `IDEA-00${i+1}: ${angle}角度创意生成`, description: `话题: ${taskDescription}\n角度: ${angle}\n\nSession: ${sessionFolder}\n输出: ideas/idea-00${i+1}.md`, activeForm: `${angle}创意生成中` }) - TaskUpdate({ taskId: ideaIds[i], owner: "ideator" }) + TaskUpdate({ taskId: ideaIds[i], owner: ideatorName }) }) // CHALLENGE-001: 批量挑战 (blockedBy all IDEA-001..003) diff --git a/.claude/skills/team-brainstorm/roles/ideator.md b/.claude/skills/team-brainstorm/roles/ideator.md index 3d7de836..46db2b7a 100644 --- a/.claude/skills/team-brainstorm/roles/ideator.md +++ b/.claude/skills/team-brainstorm/roles/ideator.md @@ -39,10 +39,14 @@ ### Phase 1: Task Discovery ```javascript +// Parse agent name for parallel instances (e.g., ideator-1, ideator-2) +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'ideator' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('IDEA-') && - t.owner === 'ideator' && + t.owner === agentName && // Use agentName (e.g., 'ideator-1') instead of hardcoded 'ideator' t.status === 'pending' && t.blockedBy.length === 0 ) @@ -205,7 +209,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' }) // Check for next task const nextTasks = TaskList().filter(t => t.subject.startsWith('IDEA-') && - t.owner === 'ideator' && + t.owner === agentName && // Use agentName for parallel instance filtering t.status === 'pending' && t.blockedBy.length === 0 ) diff --git a/.claude/skills/team-issue/SKILL.md b/.claude/skills/team-issue/SKILL.md index d9a76837..61337657 100644 --- a/.claude/skills/team-issue/SKILL.md +++ b/.claude/skills/team-issue/SKILL.md @@ -58,6 +58,7 @@ if (!roleMatch) { const role = roleMatch[1] const teamName = "issue" +const agentName = args.match(/--agent-name[=\s]+([\w-]+)/)?.[1] || role ``` ### Role Dispatch @@ -140,7 +141,6 @@ mcp__ccw-tools__team_msg({ const TEAM_CONFIG = { name: "issue", sessionDir: ".workflow/.team-plan/issue/", - msgDir: ".workflow/.team-msg/issue/", issueDataDir: ".workflow/issues/" } ``` @@ -188,7 +188,7 @@ Bash(`ccw team log --team "issue" --from "${role}" --to "coordinator" --type " t.subject.startsWith(`${VALID_ROLES[role].prefix}-`) && - t.owner === role && + t.owner === agentName && // Use agentName (e.g., 'explorer-1') instead of role t.status === 'pending' && t.blockedBy.length === 0 ) @@ -241,12 +241,49 @@ function detectMode(issueIds, userMode) { ```javascript TeamCreate({ team_name: "issue" }) -// Explorer -Task({ - subagent_type: "general-purpose", - team_name: "issue", - name: "explorer", - prompt: `你是 team "issue" 的 EXPLORER。 +// Explorer — conditional parallel spawn for Batch mode +const isBatchMode = mode === 'batch' +const maxParallelExplorers = Math.min(issueIds.length, 5) + +if (isBatchMode && issueIds.length > 1) { + // Batch mode: spawn N explorers for parallel exploration + for (let i = 0; i < maxParallelExplorers; i++) { + const agentName = `explorer-${i + 1}` + Task({ + subagent_type: "general-purpose", + team_name: "issue", + name: agentName, + prompt: `你是 team "issue" 的 EXPLORER (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 EXPLORE-* 任务时,调用 Skill(skill="team-issue", args="--role=explorer --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 EXPLORE-* 前缀任务 +- 所有输出(SendMessage、team_msg)必须带 [explorer] 标识前缀 +- 仅与 coordinator 通信,不得直接联系其他 worker +- 不得使用 TaskCreate 为其他角色创建任务 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 EXPLORE-* 任务 +2. Skill(skill="team-issue", args="--role=explorer --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage 结果给 coordinator(带 [explorer] 标识) +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + // Quick/Full mode: single explorer + Task({ + subagent_type: "general-purpose", + team_name: "issue", + name: "explorer", + prompt: `你是 team "issue" 的 EXPLORER。 当你收到 EXPLORE-* 任务时,调用 Skill(skill="team-issue", args="--role=explorer") 执行。 @@ -267,7 +304,8 @@ Task({ 2. Skill(skill="team-issue", args="--role=explorer") 执行 3. team_msg log + SendMessage 结果给 coordinator(带 [explorer] 标识) 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} // Planner Task({ @@ -351,12 +389,46 @@ Task({ 4. TaskUpdate completed → 检查下一个任务` }) -// Implementer -Task({ - subagent_type: "general-purpose", - team_name: "issue", - name: "implementer", - prompt: `你是 team "issue" 的 IMPLEMENTER。 +// Implementer — conditional parallel spawn for Batch mode (DAG parallel BUILD tasks) +if (isBatchMode && issueIds.length > 2) { + // Batch mode: spawn multiple implementers for parallel BUILD execution + const maxParallelBuilders = Math.min(issueIds.length, 3) + for (let i = 0; i < maxParallelBuilders; i++) { + const agentName = `implementer-${i + 1}` + Task({ + subagent_type: "general-purpose", + team_name: "issue", + name: agentName, + prompt: `你是 team "issue" 的 IMPLEMENTER (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 BUILD-* 任务时,调用 Skill(skill="team-issue", args="--role=implementer --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 BUILD-* 前缀任务 +- 所有输出必须带 [implementer] 标识前缀 +- 仅与 coordinator 通信 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 BUILD-* 任务 +2. Skill(skill="team-issue", args="--role=implementer --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage 结果给 coordinator +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + // Quick/Full mode: single implementer + Task({ + subagent_type: "general-purpose", + team_name: "issue", + name: "implementer", + prompt: `你是 team "issue" 的 IMPLEMENTER。 当你收到 BUILD-* 任务时,调用 Skill(skill="team-issue", args="--role=implementer") 执行。 @@ -376,7 +448,8 @@ Task({ 2. Skill(skill="team-issue", args="--role=implementer") 执行 3. team_msg log + SendMessage 结果给 coordinator 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} ``` ## Error Handling diff --git a/.claude/skills/team-issue/roles/coordinator.md b/.claude/skills/team-issue/roles/coordinator.md index 1b99d976..5d9166b9 100644 --- a/.claude/skills/team-issue/roles/coordinator.md +++ b/.claude/skills/team-issue/roles/coordinator.md @@ -258,19 +258,22 @@ for (const issueId of issueIds) { // Group issues into batches const exploreBatches = chunkArray(issueIds, 5) // max 5 parallel const solveBatches = chunkArray(issueIds, 3) // max 3 parallel +const maxParallelExplorers = Math.min(issueIds.length, 5) +const maxParallelBuilders = Math.min(issueIds.length, 3) -// Create EXPLORE tasks — all parallel within each batch, batches run in rolling window -// Each batch of ≤5 runs concurrently; next batch starts when current batch completes +// Create EXPLORE tasks — distribute across parallel explorer agents (round-robin) const exploreTaskIds = [] let prevBatchLastId = null for (const [batchIdx, batch] of exploreBatches.entries()) { const batchTaskIds = [] - for (const issueId of batch) { + for (const [inBatchIdx, issueId] of batch.entries()) { + const globalIdx = exploreTaskIds.length + const explorerName = `explorer-${(globalIdx % maxParallelExplorers) + 1}` const id = TaskCreate({ - subject: `EXPLORE-${String(exploreTaskIds.length + 1).padStart(3, '0')}: Context for ${issueId}`, + subject: `EXPLORE-${String(globalIdx + 1).padStart(3, '0')}: Context for ${issueId}`, description: `Batch ${batchIdx + 1}: Explore codebase context for issue ${issueId}.`, activeForm: `Exploring ${issueId}`, - owner: "explorer", + owner: explorerName, // Distribute across explorer-1, explorer-2, etc. // Only block on previous batch's LAST task (not within same batch) addBlockedBy: prevBatchLastId ? [prevBatchLastId] : [] }) @@ -312,6 +315,7 @@ const marshalId = TaskCreate({ }) // BUILD tasks created dynamically after MARSHAL completes (based on DAG) +// Each BUILD-* task is assigned to implementer-1, implementer-2, etc. (round-robin) // Each BUILD-* task description MUST include: // execution_method: ${executionMethod} // code_review: ${codeReviewTool} diff --git a/.claude/skills/team-issue/roles/explorer.md b/.claude/skills/team-issue/roles/explorer.md index 67647d8b..0c8a108b 100644 --- a/.claude/skills/team-issue/roles/explorer.md +++ b/.claude/skills/team-issue/roles/explorer.md @@ -55,10 +55,14 @@ Issue 上下文分析、代码探索、依赖识别、影响面评估。为 plan ### Phase 1: Task Discovery ```javascript +// Parse agent name for parallel instances (e.g., explorer-1, explorer-2) +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'explorer' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('EXPLORE-') && - t.owner === 'explorer' && + t.owner === agentName && // Use agentName (e.g., 'explorer-1') instead of hardcoded 'explorer' t.status === 'pending' && t.blockedBy.length === 0 ) @@ -216,7 +220,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' }) // Check for next task const nextTasks = TaskList().filter(t => t.subject.startsWith('EXPLORE-') && - t.owner === 'explorer' && + t.owner === agentName && // Use agentName for parallel instance filtering t.status === 'pending' && t.blockedBy.length === 0 ) diff --git a/.claude/skills/team-issue/roles/implementer.md b/.claude/skills/team-issue/roles/implementer.md index 51ba8061..39ef7f29 100644 --- a/.claude/skills/team-issue/roles/implementer.md +++ b/.claude/skills/team-issue/roles/implementer.md @@ -135,10 +135,14 @@ Dependencies: ${explorerContext.dependencies?.join(', ') || 'N/A'} ### Phase 1: Task Discovery ```javascript +// Parse agent name for parallel instances (e.g., implementer-1, implementer-2) +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'implementer' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('BUILD-') && - t.owner === 'implementer' && + t.owner === agentName && // Use agentName (e.g., 'implementer-1') instead of hardcoded 'implementer' t.status === 'pending' && t.blockedBy.length === 0 ) @@ -370,7 +374,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' }) // Check for next BUILD-* task (parallel BUILD tasks or new batches) const nextTasks = TaskList().filter(t => t.subject.startsWith('BUILD-') && - t.owner === 'implementer' && + t.owner === agentName && // Use agentName for parallel instance filtering t.status === 'pending' && t.blockedBy.length === 0 ) diff --git a/.claude/skills/team-quality-assurance/SKILL.md b/.claude/skills/team-quality-assurance/SKILL.md index dad732a7..b3cee33f 100644 --- a/.claude/skills/team-quality-assurance/SKILL.md +++ b/.claude/skills/team-quality-assurance/SKILL.md @@ -145,7 +145,6 @@ mcp__ccw-tools__team_msg({ summary: `[${role}] ...` }) const TEAM_CONFIG = { name: "quality-assurance", sessionDir: ".workflow/.team/QA-{slug}-{date}/", - msgDir: ".workflow/.team-msg/quality-assurance/", sharedMemory: "shared-memory.json", testLayers: { L1: { name: "Unit Tests", coverage_target: 80 }, @@ -338,12 +337,43 @@ Task({ 4. TaskUpdate completed → 检查下一个任务` }) -// Generator -Task({ - subagent_type: "general-purpose", - team_name: teamName, - name: "generator", - prompt: `你是 team "${teamName}" 的 GENERATOR。 +// Generator — parallel instances for full mode (generator-1, generator-2) +const isFullMode = qaMode === 'full' + +if (isFullMode) { + for (let i = 1; i <= 2; i++) { + const agentName = `generator-${i}` + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: agentName, + prompt: `你是 team "${teamName}" 的 GENERATOR (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 QAGEN-* 任务时,调用 Skill(skill="team-quality-assurance", args="--role=generator --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 QAGEN-* 前缀任务 +- 所有输出必须带 [generator] 标识前缀 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 QAGEN-* 任务 +2. Skill(skill="team-quality-assurance", args="--role=generator --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "generator", + prompt: `你是 team "${teamName}" 的 GENERATOR。 当你收到 QAGEN-* 任务时,调用 Skill(skill="team-quality-assurance", args="--role=generator") 执行。 @@ -361,14 +391,44 @@ Task({ 2. Skill(skill="team-quality-assurance", args="--role=generator") 执行 3. team_msg log + SendMessage 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} -// Executor -Task({ - subagent_type: "general-purpose", - team_name: teamName, - name: "executor", - prompt: `你是 team "${teamName}" 的 EXECUTOR。 +// Executor — parallel instances for full mode (executor-1, executor-2) +if (isFullMode) { + for (let i = 1; i <= 2; i++) { + const agentName = `executor-${i}` + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: agentName, + prompt: `你是 team "${teamName}" 的 EXECUTOR (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 QARUN-* 任务时,调用 Skill(skill="team-quality-assurance", args="--role=executor --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 QARUN-* 前缀任务 +- 所有输出必须带 [executor] 标识前缀 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 QARUN-* 任务 +2. Skill(skill="team-quality-assurance", args="--role=executor --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "executor", + prompt: `你是 team "${teamName}" 的 EXECUTOR。 当你收到 QARUN-* 任务时,调用 Skill(skill="team-quality-assurance", args="--role=executor") 执行。 @@ -386,7 +446,8 @@ Task({ 2. Skill(skill="team-quality-assurance", args="--role=executor") 执行 3. team_msg log + SendMessage 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} // Analyst Task({ diff --git a/.claude/skills/team-quality-assurance/roles/coordinator/commands/dispatch.md b/.claude/skills/team-quality-assurance/roles/coordinator/commands/dispatch.md index c3591b14..7fd92279 100644 --- a/.claude/skills/team-quality-assurance/roles/coordinator/commands/dispatch.md +++ b/.claude/skills/team-quality-assurance/roles/coordinator/commands/dispatch.md @@ -43,10 +43,10 @@ function buildPipeline(qaMode, sessionFolder, taskDescription) { 'full': [ { prefix: 'SCOUT', owner: 'scout', desc: '多视角问题扫描', blockedBy: [] }, { prefix: 'QASTRAT', owner: 'strategist', desc: '测试策略制定', blockedBy: ['SCOUT'] }, - { prefix: 'QAGEN-L1', owner: 'generator', desc: '测试代码生成 (L1)', meta: 'layer: L1', blockedBy: ['QASTRAT'] }, - { prefix: 'QAGEN-L2', owner: 'generator', desc: '测试代码生成 (L2)', meta: 'layer: L2', blockedBy: ['QASTRAT'] }, - { prefix: 'QARUN-L1', owner: 'executor', desc: '测试执行 (L1)', meta: 'layer: L1', blockedBy: ['QAGEN-L1'] }, - { prefix: 'QARUN-L2', owner: 'executor', desc: '测试执行 (L2)', meta: 'layer: L2', blockedBy: ['QAGEN-L2'] }, + { prefix: 'QAGEN-L1', owner: 'generator-1', desc: '测试代码生成 (L1)', meta: 'layer: L1', blockedBy: ['QASTRAT'] }, + { prefix: 'QAGEN-L2', owner: 'generator-2', desc: '测试代码生成 (L2)', meta: 'layer: L2', blockedBy: ['QASTRAT'] }, + { prefix: 'QARUN-L1', owner: 'executor-1', desc: '测试执行 (L1)', meta: 'layer: L1', blockedBy: ['QAGEN-L1'] }, + { prefix: 'QARUN-L2', owner: 'executor-2', desc: '测试执行 (L2)', meta: 'layer: L2', blockedBy: ['QAGEN-L2'] }, { prefix: 'QAANA', owner: 'analyst', desc: '质量分析报告', blockedBy: ['QARUN-L1', 'QARUN-L2'] }, { prefix: 'SCOUT-REG', owner: 'scout', desc: '回归扫描', blockedBy: ['QAANA'] } ] diff --git a/.claude/skills/team-quality-assurance/roles/executor/role.md b/.claude/skills/team-quality-assurance/roles/executor/role.md index 0d927565..2920efb1 100644 --- a/.claude/skills/team-quality-assurance/roles/executor/role.md +++ b/.claude/skills/team-quality-assurance/roles/executor/role.md @@ -54,10 +54,14 @@ ### Phase 1: Task Discovery ```javascript +// Parse agent name for parallel instances (e.g., executor-1, executor-2) +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'executor' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('QARUN-') && - t.owner === 'executor' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) @@ -234,7 +238,7 @@ SendMessage({ TaskUpdate({ taskId: task.id, status: 'completed' }) const nextTasks = TaskList().filter(t => - t.subject.startsWith('QARUN-') && t.owner === 'executor' && + t.subject.startsWith('QARUN-') && t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) if (nextTasks.length > 0) { /* back to Phase 1 */ } diff --git a/.claude/skills/team-quality-assurance/roles/generator/role.md b/.claude/skills/team-quality-assurance/roles/generator/role.md index 90e7d58a..fb1d0388 100644 --- a/.claude/skills/team-quality-assurance/roles/generator/role.md +++ b/.claude/skills/team-quality-assurance/roles/generator/role.md @@ -59,10 +59,14 @@ ### Phase 1: Task Discovery ```javascript +// Parse agent name for parallel instances (e.g., generator-1, generator-2) +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'generator' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('QAGEN-') && - t.owner === 'generator' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) @@ -264,7 +268,7 @@ ${generatedTests.map(f => `- ${f}`).join('\n')}`, TaskUpdate({ taskId: task.id, status: 'completed' }) const nextTasks = TaskList().filter(t => - t.subject.startsWith('QAGEN-') && t.owner === 'generator' && + t.subject.startsWith('QAGEN-') && t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) if (nextTasks.length > 0) { /* back to Phase 1 */ } diff --git a/.claude/skills/team-ultra-analyze/SKILL.md b/.claude/skills/team-ultra-analyze/SKILL.md index a013b166..fd20d62e 100644 --- a/.claude/skills/team-ultra-analyze/SKILL.md +++ b/.claude/skills/team-ultra-analyze/SKILL.md @@ -57,7 +57,7 @@ roles/ ### Input Parsing -Parse `$ARGUMENTS` to extract `--role`: +Parse `$ARGUMENTS` to extract `--role` and optional `--agent-name`: ```javascript const args = "$ARGUMENTS" @@ -69,6 +69,9 @@ if (!roleMatch) { const role = roleMatch[1] const teamName = args.match(/--team[=\s]+([\w-]+)/)?.[1] || "ultra-analyze" +// --agent-name for parallel instances (e.g., explorer-1, analyst-2) +// Passed through to role.md for task discovery filtering +const agentName = args.match(/--agent-name[=\s]+([\w-]+)/)?.[1] || role ``` ### Role Dispatch @@ -139,7 +142,6 @@ mcp__ccw-tools__team_msg({ summary: `[${role}] ...` }) const TEAM_CONFIG = { name: "ultra-analyze", sessionDir: ".workflow/.team/UAN-{slug}-{date}/", - msgDir: ".workflow/.team-msg/ultra-analyze/", sharedMemory: "shared-memory.json", analysisDimensions: ["architecture", "implementation", "performance", "security", "concept", "comparison", "decision"], maxDiscussionRounds: 5 @@ -272,12 +274,51 @@ coordinator(AskUser) → DISCUSS-N(deepen) → [optional ANALYZE-fix] → coordi ```javascript TeamCreate({ team_name: teamName }) -// Explorer -Task({ - subagent_type: "general-purpose", - team_name: teamName, - name: "explorer", - prompt: `你是 team "${teamName}" 的 EXPLORER。 +// ── Determine parallel agent count ── +const perspectiveCount = selectedPerspectives.length +const isParallel = perspectiveCount > 1 + +// ── Explorers ── +// Quick mode (1 perspective): single "explorer" +// Standard/Deep mode (N perspectives): "explorer-1", "explorer-2", ... +if (isParallel) { + for (let i = 0; i < perspectiveCount; i++) { + const agentName = `explorer-${i + 1}` + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: agentName, + prompt: `你是 team "${teamName}" 的 EXPLORER (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 EXPLORE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=explorer --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 EXPLORE-* 前缀任务 +- 所有输出(SendMessage、team_msg)必须带 [explorer] 标识前缀 +- 仅与 coordinator 通信,不得直接联系其他 worker +- 不得使用 TaskCreate 为其他角色创建任务 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 EXPLORE-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=explorer --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage 结果给 coordinator(带 [explorer] 标识) +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + // Single explorer for quick mode + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "explorer", + prompt: `你是 team "${teamName}" 的 EXPLORER。 当你收到 EXPLORE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=explorer") 执行。 @@ -298,14 +339,47 @@ Task({ 2. Skill(skill="team-ultra-analyze", args="--role=explorer") 执行 3. team_msg log + SendMessage 结果给 coordinator(带 [explorer] 标识) 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} -// Analyst -Task({ - subagent_type: "general-purpose", - team_name: teamName, - name: "analyst", - prompt: `你是 team "${teamName}" 的 ANALYST。 +// ── Analysts ── +// Same pattern: parallel mode spawns N analysts, quick mode spawns 1 +if (isParallel) { + for (let i = 0; i < perspectiveCount; i++) { + const agentName = `analyst-${i + 1}` + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: agentName, + prompt: `你是 team "${teamName}" 的 ANALYST (${agentName})。 +你的 agent 名称是 "${agentName}",任务发现时用此名称匹配 owner。 + +当你收到 ANALYZE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=analyst --agent-name=${agentName}") 执行。 + +当前需求: ${taskDescription} +约束: ${constraints} + +## 角色准则(强制) +- 你只能处理 owner 为 "${agentName}" 的 ANALYZE-* 前缀任务 +- 所有输出必须带 [analyst] 标识前缀 +- 仅与 coordinator 通信 + +## 消息总线(必须) +每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。 + +工作流程: +1. TaskList → 找到 owner === "${agentName}" 的 ANALYZE-* 任务 +2. Skill(skill="team-ultra-analyze", args="--role=analyst --agent-name=${agentName}") 执行 +3. team_msg log + SendMessage 结果给 coordinator +4. TaskUpdate completed → 检查下一个任务` + }) + } +} else { + Task({ + subagent_type: "general-purpose", + team_name: teamName, + name: "analyst", + prompt: `你是 team "${teamName}" 的 ANALYST。 当你收到 ANALYZE-* 任务时,调用 Skill(skill="team-ultra-analyze", args="--role=analyst") 执行。 @@ -325,9 +399,10 @@ Task({ 2. Skill(skill="team-ultra-analyze", args="--role=analyst") 执行 3. team_msg log + SendMessage 结果给 coordinator 4. TaskUpdate completed → 检查下一个任务` -}) + }) +} -// Discussant +// ── Discussant (always single) ── Task({ subagent_type: "general-purpose", team_name: teamName, @@ -352,7 +427,7 @@ Task({ 4. TaskUpdate completed → 检查下一个任务` }) -// Synthesizer +// ── Synthesizer (always single) ── Task({ subagent_type: "general-purpose", team_name: teamName, diff --git a/.claude/skills/team-ultra-analyze/roles/analyst/role.md b/.claude/skills/team-ultra-analyze/roles/analyst/role.md index fc80b98a..6e0feb7a 100644 --- a/.claude/skills/team-ultra-analyze/roles/analyst/role.md +++ b/.claude/skills/team-ultra-analyze/roles/analyst/role.md @@ -61,10 +61,14 @@ ### Phase 1: Task Discovery ```javascript +// Parse agent name from --agent-name arg (for parallel instances) or default to 'analyst' +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'analyst' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('ANALYZE-') && - t.owner === 'analyst' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) @@ -269,7 +273,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' }) // Check for next task const nextTasks = TaskList().filter(t => t.subject.startsWith('ANALYZE-') && - t.owner === 'analyst' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) diff --git a/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md index 3097f363..0c48534a 100644 --- a/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md +++ b/.claude/skills/team-ultra-analyze/roles/coordinator/commands/dispatch.md @@ -39,23 +39,26 @@ function buildPipeline(pipelineMode, perspectives, sessionFolder, taskDescriptio function buildStandardPipeline(perspectives, dimensions) { const stages = [] const perspectiveList = perspectives.length > 0 ? perspectives : ['technical'] + const isParallel = perspectiveList.length > 1 - // Parallel explorations + // Parallel explorations — each gets a distinct agent name for true parallelism perspectiveList.forEach((p, i) => { const num = String(i + 1).padStart(3, '0') + const explorerName = isParallel ? `explorer-${i + 1}` : 'explorer' stages.push({ - prefix: 'EXPLORE', suffix: num, owner: 'explorer', + prefix: 'EXPLORE', suffix: num, owner: explorerName, desc: `代码库探索 (${p})`, meta: `perspective: ${p}\ndimensions: ${dimensions.join(', ')}`, blockedBy: [] }) }) - // Parallel analyses (blocked by corresponding exploration) + // Parallel analyses — each gets a distinct agent name for true parallelism perspectiveList.forEach((p, i) => { const num = String(i + 1).padStart(3, '0') + const analystName = isParallel ? `analyst-${i + 1}` : 'analyst' stages.push({ - prefix: 'ANALYZE', suffix: num, owner: 'analyst', + prefix: 'ANALYZE', suffix: num, owner: analystName, desc: `深度分析 (${p})`, meta: `perspective: ${p}\ndimensions: ${dimensions.join(', ')}`, blockedBy: [`EXPLORE-${num}`] diff --git a/.claude/skills/team-ultra-analyze/roles/coordinator/role.md b/.claude/skills/team-ultra-analyze/roles/coordinator/role.md index 838d195a..7f42a88b 100644 --- a/.claude/skills/team-ultra-analyze/roles/coordinator/role.md +++ b/.claude/skills/team-ultra-analyze/roles/coordinator/role.md @@ -211,7 +211,10 @@ Write(`${sessionFolder}/discussion.md`, `# Analysis Discussion TeamCreate({ team_name: teamName }) // Spawn teammates (see SKILL.md Coordinator Spawn Template) -// Explorer, Analyst, Discussant, Synthesizer +// Quick mode: 1 explorer + 1 analyst (single agents) +// Standard/Deep mode: N explorers + N analysts (parallel agents with distinct names) +// explorer-1, explorer-2... / analyst-1, analyst-2... for true parallel execution +// Discussant and Synthesizer are always single instances ``` ### Phase 3: Create Task Chain diff --git a/.claude/skills/team-ultra-analyze/roles/explorer/role.md b/.claude/skills/team-ultra-analyze/roles/explorer/role.md index bdd46fd9..1d61cb64 100644 --- a/.claude/skills/team-ultra-analyze/roles/explorer/role.md +++ b/.claude/skills/team-ultra-analyze/roles/explorer/role.md @@ -58,10 +58,14 @@ ### Phase 1: Task Discovery ```javascript +// Parse agent name from --agent-name arg (for parallel instances) or default to 'explorer' +const agentNameMatch = args.match(/--agent-name[=\s]+([\w-]+)/) +const agentName = agentNameMatch ? agentNameMatch[1] : 'explorer' + const tasks = TaskList() const myTasks = tasks.filter(t => t.subject.startsWith('EXPLORE-') && - t.owner === 'explorer' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 ) @@ -222,7 +226,7 @@ TaskUpdate({ taskId: task.id, status: 'completed' }) // Check for next task const nextTasks = TaskList().filter(t => t.subject.startsWith('EXPLORE-') && - t.owner === 'explorer' && + t.owner === agentName && t.status === 'pending' && t.blockedBy.length === 0 )