mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
feat: merge 10 team commands into unified team-lifecycle skill
Consolidate coordinate, plan, execute, test, review, spec-coordinate, spec-analyst, spec-writer, spec-discuss, spec-reviewer into a single team-lifecycle skill with role-based routing (Pattern B architecture). - SKILL.md: role router with 8 roles, shared message bus, 3-mode pipeline - coordinator: unified orchestrator for spec-only/impl-only/full-lifecycle - 7 worker roles: analyst, writer, discussant, planner, executor, tester, reviewer - reviewer: dual-prefix (REVIEW-*/QUALITY-*) auto-switching code/spec review - Each role: 5-phase execution, message bus with CLI fallback, error handling
This commit is contained in:
@@ -1,228 +0,0 @@
|
||||
---
|
||||
name: coordinate
|
||||
description: Team coordinator - 需求澄清、MVP路线图、创建持久化agent team、跨阶段协调plan/execute/test/review
|
||||
argument-hint: "[--team-name=NAME] \"task description\""
|
||||
allowed-tools: TeamCreate(*), TeamDelete(*), SendMessage(*), TaskCreate(*), TaskUpdate(*), TaskList(*), TaskGet(*), Task(*), AskUserQuestion(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Grep(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Coordinate Command (/team:coordinate)
|
||||
|
||||
纯调度协调器。需求澄清 → 创建 Team → 创建任务链 → 协调消息 → 持久循环。具体工作逻辑由各 teammate 调用自己的 skill 完成。
|
||||
|
||||
## 消息总线
|
||||
|
||||
所有 teammate 在 SendMessage 的**同时**必须调用 `mcp__ccw-tools__team_msg` 记录消息,实现持久化和用户可观测:
|
||||
|
||||
```javascript
|
||||
// 记录消息(每个 teammate 发 SendMessage 前调用)
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_ready", summary: "Plan就绪: 3个task", ref: ".workflow/.team-plan/auth-team/plan.json" })
|
||||
|
||||
// Coordinator 查看全部消息
|
||||
mcp__ccw-tools__team_msg({ operation: "list", team: teamName })
|
||||
|
||||
// 按角色过滤
|
||||
mcp__ccw-tools__team_msg({ operation: "list", team: teamName, from: "tester", last: 5 })
|
||||
|
||||
// 查看团队状态
|
||||
mcp__ccw-tools__team_msg({ operation: "status", team: teamName })
|
||||
|
||||
// 读取特定消息
|
||||
mcp__ccw-tools__team_msg({ operation: "read", team: teamName, id: "MSG-003" })
|
||||
```
|
||||
|
||||
**日志位置**: `.workflow/.team-msg/{team-name}/messages.jsonl`
|
||||
**消息类型**: `plan_ready | plan_approved | plan_revision | task_unblocked | impl_complete | impl_progress | test_result | review_result | fix_required | error | shutdown`
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
// log
|
||||
Bash(`ccw team log --team "${teamName}" --from "coordinator" --to "planner" --type "plan_approved" --summary "Plan已批准" --json`)
|
||||
// list
|
||||
Bash(`ccw team list --team "${teamName}" --last 10 --json`)
|
||||
// list (带过滤)
|
||||
Bash(`ccw team list --team "${teamName}" --from "tester" --last 5 --json`)
|
||||
// status
|
||||
Bash(`ccw team status --team "${teamName}" --json`)
|
||||
// read
|
||||
Bash(`ccw team read --team "${teamName}" --id "MSG-003" --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team <operation> --team <team> [--from/--to/--type/--summary/--ref/--data/--id/--last] [--json]`
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
/team:coordinate "实现用户认证模块"
|
||||
/team:coordinate --team-name=auth-team "实现JWT刷新令牌"
|
||||
```
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
需求 → [PLAN: planner] → coordinator 审批 → [IMPL: executor] → [TEST + REVIEW: tester] → 汇报 → 等待新需求/关闭
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 1: 需求澄清
|
||||
|
||||
解析 `$ARGUMENTS` 获取 `--team-name` 和任务描述。使用 AskUserQuestion 收集:
|
||||
- MVP 范围(最小可行 / 功能完整 / 全面实现)
|
||||
- 关键约束(向后兼容 / 遵循模式 / 测试覆盖 / 性能敏感)
|
||||
|
||||
简单任务可跳过澄清。
|
||||
|
||||
### Phase 2: 创建 Team + Spawn 3 Teammates
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
```
|
||||
|
||||
**Spawn 时只传角色和需求,工作细节由 skill 定义**:
|
||||
|
||||
```javascript
|
||||
// Planner
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "planner",
|
||||
mode: "plan",
|
||||
prompt: `你是 team "${teamName}" 的 PLANNER。
|
||||
|
||||
当你收到 PLAN 任务时,调用 Skill(skill="team:plan") 执行规划工作。
|
||||
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
复杂度: ${complexity}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "planner", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到分配给你的 PLAN-* 任务
|
||||
2. Skill(skill="team:plan") 执行探索和规划
|
||||
3. team_msg log + SendMessage 将 plan 摘要发给 coordinator
|
||||
4. 等待 coordinator 审批或修改反馈
|
||||
5. 审批通过 → TaskUpdate completed → 检查下一个 PLAN 任务`
|
||||
})
|
||||
|
||||
// Executor
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "executor",
|
||||
prompt: `你是 team "${teamName}" 的 EXECUTOR。
|
||||
|
||||
当你收到 IMPL 任务时,调用 Skill(skill="team:execute") 执行代码实现。
|
||||
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "executor", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到未阻塞的 IMPL-* 任务
|
||||
2. Skill(skill="team:execute") 执行实现
|
||||
3. team_msg log + SendMessage 报告完成状态和变更文件
|
||||
4. TaskUpdate completed → 检查下一个 IMPL 任务`
|
||||
})
|
||||
|
||||
// Tester (同时处理 TEST 和 REVIEW)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "tester",
|
||||
prompt: `你是 team "${teamName}" 的 TESTER,同时负责测试和审查。
|
||||
|
||||
- 收到 TEST-* 任务 → 调用 Skill(skill="team:test") 执行测试修复循环
|
||||
- 收到 REVIEW-* 任务 → 调用 Skill(skill="team:review") 执行代码审查
|
||||
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "tester", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到未阻塞的 TEST-* 或 REVIEW-* 任务
|
||||
2. 根据任务类型调用对应 Skill
|
||||
3. team_msg log + SendMessage 报告结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
```
|
||||
|
||||
### Phase 3: 创建任务链
|
||||
|
||||
```javascript
|
||||
// PLAN-001 → IMPL-001 → TEST-001 + REVIEW-001
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\n写入: .workflow/.team-plan/${teamName}/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner" })
|
||||
|
||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nPlan: .workflow/.team-plan/${teamName}/plan.json`, activeForm: "实现中" })
|
||||
TaskUpdate({ taskId: implId, owner: "executor", addBlockedBy: [planId] })
|
||||
|
||||
TaskCreate({ subject: "TEST-001: 测试修复循环", description: `${taskDescription}`, activeForm: "测试中" })
|
||||
TaskUpdate({ taskId: testId, owner: "tester", addBlockedBy: [implId] })
|
||||
|
||||
TaskCreate({ subject: "REVIEW-001: 代码审查与需求验证", description: `${taskDescription}\n\nPlan: .workflow/.team-plan/${teamName}/plan.json`, activeForm: "审查中" })
|
||||
TaskUpdate({ taskId: reviewId, owner: "tester", addBlockedBy: [implId] })
|
||||
```
|
||||
|
||||
### Phase 4: 协调主循环
|
||||
|
||||
接收 teammate 消息,根据内容做调度决策。**每次做出决策前先 `team_msg list` 查看最近消息,每次做出决策后 `team_msg log` 记录**:
|
||||
|
||||
| 收到消息 | 操作 |
|
||||
|----------|------|
|
||||
| Planner: plan 就绪 | 读取 plan → 审批/修改 → team_msg log(plan_approved/plan_revision) → TaskUpdate + SendMessage |
|
||||
| Executor: 实现完成 | team_msg log(task_unblocked) → TaskUpdate IMPL completed → SendMessage 通知 tester |
|
||||
| Tester: 测试结果 >= 95% | team_msg log(test_result) → TaskUpdate TEST completed |
|
||||
| Tester: 测试结果 < 95% 且迭代 > 5 | team_msg log(error) → 上报用户 |
|
||||
| Tester: 审查无 critical | team_msg log(review_result) → TaskUpdate REVIEW completed |
|
||||
| Tester: 审查发现 critical | team_msg log(fix_required) → TaskCreate IMPL-fix → 分配 executor |
|
||||
| 所有任务 completed | → Phase 5 |
|
||||
|
||||
**用户可随时查看团队状态**:
|
||||
```bash
|
||||
# 用户在任意时刻调用查看
|
||||
mcp__ccw-tools__team_msg({ operation: "status", team: teamName })
|
||||
mcp__ccw-tools__team_msg({ operation: "list", team: teamName, last: 10 })
|
||||
```
|
||||
|
||||
### Phase 5: 汇报 + 持久循环
|
||||
|
||||
汇总变更文件、测试通过率、审查结果报告用户。
|
||||
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "当前需求已完成。下一步:",
|
||||
header: "Next",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "新需求", description: "提交新需求给当前团队" },
|
||||
{ label: "关闭团队", description: "关闭所有 teammate 并清理" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
// 新需求 → 回到 Phase 1(复用 team,新建 PLAN/IMPL/TEST/REVIEW 任务)
|
||||
// 关闭 → shutdown_request 给每个 teammate → TeamDelete()
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| Teammate 无响应 | 发追踪消息,2次无响应 → 重新 spawn |
|
||||
| Plan 被拒 3+ 次 | Coordinator 自行规划 |
|
||||
| 测试卡在 <80% 超 5 次迭代 | 上报用户 |
|
||||
| Review 发现 critical | 创建 IMPL-fix 任务给 executor |
|
||||
@@ -1,373 +0,0 @@
|
||||
---
|
||||
name: execute
|
||||
description: Team executor - 实现已批准的计划、编写代码、报告进度
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Execute Command (/team:execute)
|
||||
|
||||
## Overview
|
||||
|
||||
Team executor role command. Operates as a teammate within an Agent Team, responsible for implementing approved plans by writing code, self-validating, and reporting progress to the coordinator.
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (IMPL-* tasks)
|
||||
- Plan loading and task decomposition
|
||||
- Code implementation following plan files list
|
||||
- Self-validation: syntax checks, acceptance criteria verification
|
||||
- Progress reporting to coordinator
|
||||
- Sub-agent delegation for complex tasks
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `executor`
|
||||
**Responsibility**: Load plan → Implement code → Self-validate → Report completion
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `impl_progress` | executor → coordinator | 完成一个 batch/子任务 | 报告当前进度百分比和完成的子任务 |
|
||||
| `impl_complete` | executor → coordinator | 全部实现完成 | 附带变更文件列表和 acceptance 状态 |
|
||||
| `error` | executor → coordinator | 遇到阻塞问题 | Plan 文件缺失、文件冲突、子代理失败等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 进度更新
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_progress", summary: "Batch 1/3 完成: auth middleware 已实现", data: { batch: 1, total: 3, files: ["src/middleware/auth.ts"] } })
|
||||
|
||||
// 实现完成
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_complete", summary: "IMPL-001完成: 5个文件变更, acceptance全部满足", data: { changedFiles: 5, syntaxClean: true } })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "error", summary: "plan.json路径无效, 无法加载实现计划" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "executor" --to "coordinator" --type "impl_complete" --summary "IMPL-001完成: 5个文件变更" --json`)
|
||||
|
||||
// 带 data 参数
|
||||
Bash(`ccw team log --team "${teamName}" --from "executor" --to "coordinator" --type "impl_progress" --summary "Batch 1/3 完成" --data '{"batch":1,"total":3}' --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from executor --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task & Plan Loading
|
||||
├─ TaskList to find unblocked IMPL-* tasks assigned to me
|
||||
├─ TaskGet to read full task details
|
||||
├─ TaskUpdate to mark in_progress
|
||||
└─ Load plan.json from plan path in task description
|
||||
|
||||
Phase 2: Task Grouping
|
||||
├─ Extract depends_on from plan tasks
|
||||
├─ Independent tasks → parallel batch
|
||||
└─ Dependent tasks → sequential batches
|
||||
|
||||
Phase 3: Code Implementation
|
||||
├─ For each task in plan:
|
||||
│ ├─ Read files list
|
||||
│ ├─ Read reference patterns
|
||||
│ ├─ Implement changes (Edit/Write)
|
||||
│ ├─ Complex tasks → code-developer sub-agent
|
||||
│ └─ Simple tasks → direct file editing
|
||||
└─ SendMessage progress updates for complex tasks
|
||||
|
||||
Phase 4: Self-Validation
|
||||
├─ Syntax check (tsc --noEmit for TypeScript)
|
||||
├─ Verify acceptance criteria from plan
|
||||
├─ Run affected unit tests (if identifiable)
|
||||
└─ Fix any immediate issues
|
||||
|
||||
Phase 5: Completion Report
|
||||
├─ Compile changed files list
|
||||
├─ Summarize acceptance criteria status
|
||||
├─ SendMessage report to coordinator
|
||||
├─ Mark IMPL task completed
|
||||
└─ Check TaskList for next IMPL task
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task & Plan Loading
|
||||
|
||||
```javascript
|
||||
// Find my assigned IMPL tasks
|
||||
const tasks = TaskList()
|
||||
const myImplTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('IMPL-') &&
|
||||
t.owner === 'executor' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0 // Not blocked
|
||||
)
|
||||
|
||||
if (myImplTasks.length === 0) {
|
||||
// No tasks available, idle
|
||||
return
|
||||
}
|
||||
|
||||
// Pick first available task (lowest ID)
|
||||
const task = TaskGet({ taskId: myImplTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
|
||||
// Extract plan path from task description
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
const planPath = planPathMatch ? planPathMatch[0] : null
|
||||
|
||||
if (!planPath) {
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `Cannot find plan.json path in task description for ${task.subject}. Please provide plan location.`,
|
||||
summary: "Plan path not found"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const plan = JSON.parse(Read(planPath))
|
||||
```
|
||||
|
||||
### Phase 2: Task Grouping
|
||||
|
||||
```javascript
|
||||
// Extract dependencies and group tasks
|
||||
function extractDependencies(planTasks) {
|
||||
const taskIdToIndex = {}
|
||||
planTasks.forEach((t, i) => { taskIdToIndex[t.id] = i })
|
||||
|
||||
return planTasks.map((task, i) => {
|
||||
const deps = (task.depends_on || [])
|
||||
.map(depId => taskIdToIndex[depId])
|
||||
.filter(idx => idx !== undefined && idx < i)
|
||||
return { ...task, taskIndex: i, dependencies: deps }
|
||||
})
|
||||
}
|
||||
|
||||
function createBatches(planTasks) {
|
||||
const tasksWithDeps = extractDependencies(planTasks)
|
||||
const processed = new Set()
|
||||
const batches = []
|
||||
|
||||
// Phase 1: Independent tasks → single parallel batch
|
||||
const independent = tasksWithDeps.filter(t => t.dependencies.length === 0)
|
||||
if (independent.length > 0) {
|
||||
independent.forEach(t => processed.add(t.taskIndex))
|
||||
batches.push({ type: 'parallel', tasks: independent })
|
||||
}
|
||||
|
||||
// Phase 2+: Dependent tasks in order
|
||||
let remaining = tasksWithDeps.filter(t => !processed.has(t.taskIndex))
|
||||
while (remaining.length > 0) {
|
||||
const ready = remaining.filter(t => t.dependencies.every(d => processed.has(d)))
|
||||
if (ready.length === 0) break // circular dependency guard
|
||||
ready.forEach(t => processed.add(t.taskIndex))
|
||||
batches.push({ type: ready.length > 1 ? 'parallel' : 'sequential', tasks: ready })
|
||||
remaining = remaining.filter(t => !processed.has(t.taskIndex))
|
||||
}
|
||||
|
||||
return batches
|
||||
}
|
||||
|
||||
const batches = createBatches(plan.tasks)
|
||||
```
|
||||
|
||||
### Phase 3: Code Implementation
|
||||
|
||||
```javascript
|
||||
// Unified Task Prompt Builder (from lite-execute)
|
||||
function buildExecutionPrompt(planTask) {
|
||||
return `
|
||||
## ${planTask.title}
|
||||
|
||||
**Scope**: \`${planTask.scope}\` | **Action**: ${planTask.action || 'implement'}
|
||||
|
||||
### Files
|
||||
${(planTask.files || []).map(f => `- **${f.path}** → \`${f.target}\`: ${f.change}`).join('\n')}
|
||||
|
||||
### How to do it
|
||||
${planTask.description}
|
||||
|
||||
${(planTask.implementation || []).map(step => `- ${step}`).join('\n')}
|
||||
|
||||
### Reference
|
||||
- Pattern: ${planTask.reference?.pattern || 'N/A'}
|
||||
- Files: ${planTask.reference?.files?.join(', ') || 'N/A'}
|
||||
|
||||
### Done when
|
||||
${(planTask.convergence?.criteria || []).map(c => `- [ ] ${c}`).join('\n')}
|
||||
`
|
||||
}
|
||||
|
||||
// Execute each batch
|
||||
const changedFiles = []
|
||||
const previousResults = []
|
||||
|
||||
for (const batch of batches) {
|
||||
if (batch.tasks.length === 1 && isSimpleTask(batch.tasks[0])) {
|
||||
// Simple task: direct implementation
|
||||
const t = batch.tasks[0]
|
||||
// Read target files, apply modifications using Edit/Write
|
||||
for (const f of (t.files || [])) {
|
||||
const content = Read(f.path)
|
||||
// Apply change based on file entry description
|
||||
Edit({ file_path: f.path, old_string: "...", new_string: "..." })
|
||||
changedFiles.push(f.path)
|
||||
}
|
||||
} else {
|
||||
// Complex task(s): delegate to code-developer sub-agent
|
||||
const prompt = batch.tasks.map(buildExecutionPrompt).join('\n\n---\n')
|
||||
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: batch.tasks.map(t => t.title).join(' | '),
|
||||
prompt: `## Goal
|
||||
${plan.summary}
|
||||
|
||||
## Tasks
|
||||
${prompt}
|
||||
|
||||
## Context
|
||||
### Project Guidelines
|
||||
@.workflow/project-guidelines.json
|
||||
|
||||
Complete each task according to its "Done when" checklist.`
|
||||
})
|
||||
|
||||
// Collect changed files from sub-agent results
|
||||
batch.tasks.forEach(t => {
|
||||
(t.files || []).forEach(f => changedFiles.push(f.path))
|
||||
})
|
||||
}
|
||||
|
||||
previousResults.push({
|
||||
batchType: batch.type,
|
||||
tasks: batch.tasks.map(t => t.title),
|
||||
status: 'completed'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Self-Validation
|
||||
|
||||
```javascript
|
||||
// Step 1: Syntax check
|
||||
const syntaxResult = Bash(`tsc --noEmit 2>&1 || true`)
|
||||
const hasSyntaxErrors = syntaxResult.includes('error TS')
|
||||
|
||||
if (hasSyntaxErrors) {
|
||||
// Attempt to fix syntax errors
|
||||
// Parse error locations, apply fixes
|
||||
console.log('Syntax errors detected, attempting fix...')
|
||||
}
|
||||
|
||||
// Step 2: Verify acceptance criteria
|
||||
const acceptanceStatus = plan.tasks.map(t => ({
|
||||
title: t.title,
|
||||
criteria: (t.convergence?.criteria || []).map(c => ({
|
||||
criterion: c,
|
||||
met: true // Evaluate based on implementation
|
||||
}))
|
||||
}))
|
||||
|
||||
// Step 3: Run affected tests (if identifiable)
|
||||
const testFiles = changedFiles
|
||||
.map(f => f.replace(/\/src\//, '/tests/').replace(/\.(ts|js)$/, '.test.$1'))
|
||||
.filter(f => Bash(`test -f ${f} && echo exists || true`).includes('exists'))
|
||||
|
||||
if (testFiles.length > 0) {
|
||||
const testResult = Bash(`npx jest ${testFiles.join(' ')} --passWithNoTests 2>&1 || true`)
|
||||
// Parse test results
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Completion Report
|
||||
|
||||
```javascript
|
||||
// Compile report
|
||||
const report = {
|
||||
task: task.subject,
|
||||
changedFiles: [...new Set(changedFiles)],
|
||||
newFiles: changedFiles.filter(f => /* detect new files */),
|
||||
acceptanceStatus: acceptanceStatus,
|
||||
syntaxClean: !hasSyntaxErrors,
|
||||
testsPassed: testFiles.length > 0 ? testResult.includes('passed') : 'N/A'
|
||||
}
|
||||
|
||||
// Send to coordinator
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Implementation Complete
|
||||
|
||||
**Task**: ${task.subject}
|
||||
|
||||
### Changed Files
|
||||
${report.changedFiles.map(f => `- ${f}`).join('\n')}
|
||||
|
||||
### Acceptance Criteria
|
||||
${acceptanceStatus.map(t => `**${t.title}**: ${t.criteria.every(c => c.met) ? 'All met' : 'Partial'}`).join('\n')}
|
||||
|
||||
### Validation
|
||||
- Syntax: ${report.syntaxClean ? 'Clean' : 'Has errors (attempted fix)'}
|
||||
- Tests: ${report.testsPassed}
|
||||
|
||||
Implementation is ready for testing and review.`,
|
||||
summary: `IMPL complete: ${report.changedFiles.length} files changed`
|
||||
})
|
||||
|
||||
// Mark task completed
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next IMPL task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('IMPL-') &&
|
||||
t.owner === 'executor' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task → back to Phase 1
|
||||
}
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
```javascript
|
||||
function isSimpleTask(task) {
|
||||
return (task.files || []).length <= 2 &&
|
||||
!task.code_skeleton &&
|
||||
(task.risks || []).length === 0
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| Plan file not found | Notify coordinator, request plan location |
|
||||
| Syntax errors after implementation | Attempt auto-fix, report remaining errors |
|
||||
| Sub-agent failure | Retry once, then attempt direct implementation |
|
||||
| File conflict / merge issue | Notify coordinator, request guidance |
|
||||
| Test failures in self-validation | Report in completion message, let tester handle |
|
||||
| Circular dependencies in plan | Execute in plan order, ignore dependency chain |
|
||||
@@ -1,422 +0,0 @@
|
||||
---
|
||||
name: plan
|
||||
description: Team planner - 多角度代码探索、结构化实现规划、提交coordinator审批
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Plan Command (/team:plan)
|
||||
|
||||
## Overview
|
||||
|
||||
Team planner role command. Operates as a teammate within an Agent Team, responsible for multi-angle code exploration and structured implementation planning. Submits plans to the coordinator for approval.
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list
|
||||
- Multi-angle codebase exploration (architecture/security/performance/bugfix/feature)
|
||||
- Complexity-adaptive planning (Low → direct, Medium/High → agent-assisted)
|
||||
- Structured plan.json generation following schema
|
||||
- Plan submission and revision cycle with coordinator
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `planner`
|
||||
**Responsibility**: Code exploration → Implementation planning → Coordinator approval
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `plan_ready` | planner → coordinator | Plan 生成完成 | 附带 plan.json 路径和任务数摘要 |
|
||||
| `plan_revision` | planner → coordinator | Plan 修订后重新提交 | 说明修改内容 |
|
||||
| `impl_progress` | planner → coordinator | 探索阶段进展更新 | 可选,长时间探索时使用 |
|
||||
| `error` | planner → coordinator | 遇到不可恢复错误 | 探索失败、schema缺失等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// Plan 就绪
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_ready", summary: "Plan就绪: 3个task, Medium复杂度", ref: ".workflow/.team-plan/auth-impl-2026-02-09/plan.json" })
|
||||
|
||||
// Plan 修订
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_revision", summary: "已按反馈拆分task-2为两个子任务" })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "error", summary: "plan-overview-base-schema.json 未找到, 使用默认结构" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "planner" --to "coordinator" --type "plan_ready" --summary "Plan就绪: 3个task" --ref "${sessionFolder}/plan.json" --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from planner --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ Read team config to identify coordinator
|
||||
├─ TaskList to find PLAN-* tasks assigned to me
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Multi-Angle Exploration
|
||||
├─ Complexity assessment (Low/Medium/High)
|
||||
├─ Angle selection based on task type
|
||||
├─ Semantic search via mcp__ace-tool__search_context
|
||||
├─ Pattern search via Grep/Glob
|
||||
├─ Complex tasks: cli-explore-agent sub-agents
|
||||
└─ Write exploration results to session folder
|
||||
|
||||
Phase 3: Plan Generation
|
||||
├─ Read plan-overview-base-schema.json + task-schema.json for structure reference
|
||||
├─ Low complexity → Direct Claude planning
|
||||
├─ Medium/High → cli-lite-planning-agent
|
||||
└─ Output: plan.json (overview with task_ids[]) + .task/TASK-*.json (independent task files)
|
||||
|
||||
Phase 4: Submit for Approval
|
||||
├─ SendMessage plan summary to coordinator
|
||||
├─ Wait for approve/revision feedback
|
||||
└─ If revision → update plan → resubmit
|
||||
|
||||
Phase 5: Idle & Next Task
|
||||
├─ Mark current task completed
|
||||
├─ TaskList to check for new PLAN tasks
|
||||
└─ No tasks → idle (wait for coordinator assignment)
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Read team config
|
||||
const teamConfig = JSON.parse(Read(`~/.claude/teams/${teamName}/config.json`))
|
||||
|
||||
// Find my assigned PLAN tasks
|
||||
const tasks = TaskList()
|
||||
const myPlanTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('PLAN-') &&
|
||||
t.owner === 'planner' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (myPlanTasks.length === 0) {
|
||||
// No tasks available, idle
|
||||
return
|
||||
}
|
||||
|
||||
// Pick first available task (lowest ID)
|
||||
const task = TaskGet({ taskId: myPlanTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
```
|
||||
|
||||
### Phase 2: Multi-Angle Exploration
|
||||
|
||||
```javascript
|
||||
// Session setup
|
||||
const taskSlug = task.subject.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const sessionFolder = `.workflow/.team-plan/${taskSlug}-${dateStr}`
|
||||
Bash(`mkdir -p ${sessionFolder}`)
|
||||
|
||||
// Complexity assessment
|
||||
function assessComplexity(desc) {
|
||||
let score = 0
|
||||
if (/refactor|architect|restructure|模块|系统/.test(desc)) score += 2
|
||||
if (/multiple|多个|across|跨/.test(desc)) score += 2
|
||||
if (/integrate|集成|api|database/.test(desc)) score += 1
|
||||
if (/security|安全|performance|性能/.test(desc)) score += 1
|
||||
return score >= 4 ? 'High' : score >= 2 ? 'Medium' : 'Low'
|
||||
}
|
||||
|
||||
const complexity = assessComplexity(task.description)
|
||||
|
||||
// Angle selection
|
||||
const ANGLE_PRESETS = {
|
||||
architecture: ['architecture', 'dependencies', 'modularity', 'integration-points'],
|
||||
security: ['security', 'auth-patterns', 'dataflow', 'validation'],
|
||||
performance: ['performance', 'bottlenecks', 'caching', 'data-access'],
|
||||
bugfix: ['error-handling', 'dataflow', 'state-management', 'edge-cases'],
|
||||
feature: ['patterns', 'integration-points', 'testing', 'dependencies']
|
||||
}
|
||||
|
||||
function selectAngles(desc, count) {
|
||||
const text = desc.toLowerCase()
|
||||
let preset = 'feature'
|
||||
if (/refactor|architect|restructure|modular/.test(text)) preset = 'architecture'
|
||||
else if (/security|auth|permission|access/.test(text)) preset = 'security'
|
||||
else if (/performance|slow|optimi|cache/.test(text)) preset = 'performance'
|
||||
else if (/fix|bug|error|issue|broken/.test(text)) preset = 'bugfix'
|
||||
return ANGLE_PRESETS[preset].slice(0, count)
|
||||
}
|
||||
|
||||
const angleCount = complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1)
|
||||
const selectedAngles = selectAngles(task.description, angleCount)
|
||||
|
||||
// Execute exploration
|
||||
// Low complexity: direct search with mcp__ace-tool__search_context + Grep/Glob
|
||||
// Medium/High: launch cli-explore-agent sub-agents in parallel
|
||||
|
||||
if (complexity === 'Low') {
|
||||
// Direct exploration
|
||||
const results = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: task.description
|
||||
})
|
||||
// Write single exploration file
|
||||
Write(`${sessionFolder}/exploration-${selectedAngles[0]}.json`, JSON.stringify({
|
||||
project_structure: "...",
|
||||
relevant_files: [],
|
||||
patterns: [],
|
||||
dependencies: [],
|
||||
integration_points: [],
|
||||
constraints: [],
|
||||
clarification_needs: [],
|
||||
_metadata: { exploration_angle: selectedAngles[0] }
|
||||
}, null, 2))
|
||||
} else {
|
||||
// Launch parallel cli-explore-agent for each angle
|
||||
selectedAngles.forEach((angle, index) => {
|
||||
Task({
|
||||
subagent_type: "cli-explore-agent",
|
||||
run_in_background: false,
|
||||
description: `Explore: ${angle}`,
|
||||
prompt: `
|
||||
## Task Objective
|
||||
Execute **${angle}** exploration for task planning context.
|
||||
|
||||
## Output Location
|
||||
**Session Folder**: ${sessionFolder}
|
||||
**Output File**: ${sessionFolder}/exploration-${angle}.json
|
||||
|
||||
## Assigned Context
|
||||
- **Exploration Angle**: ${angle}
|
||||
- **Task Description**: ${task.description}
|
||||
- **Exploration Index**: ${index + 1} of ${selectedAngles.length}
|
||||
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Run: rg -l "{relevant_keyword}" --type ts (locate relevant files)
|
||||
2. Execute: cat ~/.ccw/workflows/cli-templates/schemas/explore-json-schema.json (get output schema)
|
||||
3. Read: .workflow/project-tech.json (if exists - technology stack)
|
||||
|
||||
## Expected Output
|
||||
Write JSON to: ${sessionFolder}/exploration-${angle}.json
|
||||
Follow explore-json-schema.json structure with ${angle}-focused findings.
|
||||
|
||||
**MANDATORY**: Every file in relevant_files MUST have:
|
||||
- **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic)
|
||||
- **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only
|
||||
- **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual
|
||||
- **key_symbols** (recommended): Key functions/classes/types relevant to task
|
||||
`
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Build explorations manifest
|
||||
const explorationManifest = {
|
||||
session_id: `${taskSlug}-${dateStr}`,
|
||||
task_description: task.description,
|
||||
complexity: complexity,
|
||||
exploration_count: selectedAngles.length,
|
||||
explorations: selectedAngles.map(angle => ({
|
||||
angle: angle,
|
||||
file: `exploration-${angle}.json`,
|
||||
path: `${sessionFolder}/exploration-${angle}.json`
|
||||
}))
|
||||
}
|
||||
Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2))
|
||||
```
|
||||
|
||||
### Phase 3: Plan Generation
|
||||
|
||||
```javascript
|
||||
// Read schema reference
|
||||
const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`)
|
||||
|
||||
if (complexity === 'Low') {
|
||||
// Direct Claude planning
|
||||
// Read all exploration files
|
||||
explorationManifest.explorations.forEach(exp => {
|
||||
const data = Read(exp.path)
|
||||
// Incorporate findings into plan
|
||||
})
|
||||
|
||||
// Generate task files in .task/ directory
|
||||
Bash(`mkdir -p ${sessionFolder}/.task`)
|
||||
|
||||
const tasks = [/* structured tasks with dependencies, files[].change, convergence.criteria */]
|
||||
const taskIds = tasks.map(t => t.id)
|
||||
|
||||
// Write individual task files following task-schema.json
|
||||
tasks.forEach(task => {
|
||||
Write(`${sessionFolder}/.task/${task.id}.json`, JSON.stringify(task, null, 2))
|
||||
})
|
||||
|
||||
// Generate plan overview following plan-overview-base-schema.json
|
||||
const plan = {
|
||||
summary: "...",
|
||||
approach: "...",
|
||||
task_ids: taskIds,
|
||||
task_count: taskIds.length,
|
||||
estimated_time: "...",
|
||||
recommended_execution: "Agent",
|
||||
complexity: "Low",
|
||||
_metadata: {
|
||||
timestamp: new Date().toISOString(),
|
||||
source: "team-planner",
|
||||
planning_mode: "direct",
|
||||
plan_type: "feature"
|
||||
}
|
||||
}
|
||||
Write(`${sessionFolder}/plan.json`, JSON.stringify(plan, null, 2))
|
||||
} else {
|
||||
// Use cli-lite-planning-agent for Medium/High
|
||||
Task({
|
||||
subagent_type: "cli-lite-planning-agent",
|
||||
run_in_background: false,
|
||||
description: "Generate detailed implementation plan",
|
||||
prompt: `
|
||||
Generate implementation plan with two-layer output.
|
||||
|
||||
## Output Location
|
||||
**Session Folder**: ${sessionFolder}
|
||||
**Output Files**:
|
||||
- ${sessionFolder}/planning-context.md
|
||||
- ${sessionFolder}/plan.json (overview with task_ids[])
|
||||
- ${sessionFolder}/.task/TASK-*.json (independent task files)
|
||||
|
||||
## Output Schema Reference
|
||||
Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json
|
||||
Execute: cat ~/.ccw/workflows/cli-templates/schemas/task-schema.json
|
||||
|
||||
## Output Format: Two-Layer Structure
|
||||
- plan.json: Overview with task_ids[] referencing .task/ files (NO tasks[] array)
|
||||
- .task/TASK-*.json: Independent task files following task-schema.json
|
||||
|
||||
plan.json required: summary, approach, task_ids, task_count, _metadata (with plan_type)
|
||||
Task files required: id, title, description, depends_on, convergence (with criteria[])
|
||||
Task fields: files[].change (not modification_points), convergence.criteria (not acceptance), test (not verification)
|
||||
|
||||
## Task Description
|
||||
${task.description}
|
||||
|
||||
## Multi-Angle Exploration Context
|
||||
${explorationManifest.explorations.map(exp => `### Exploration: ${exp.angle}
|
||||
Path: ${exp.path}`).join('\n\n')}
|
||||
|
||||
## Complexity Level
|
||||
${complexity}
|
||||
|
||||
## Requirements
|
||||
Generate plan.json + .task/*.json following schemas. Key constraints:
|
||||
- 2-7 structured tasks (group by feature/module, NOT by file)
|
||||
- Each task file: id, title, description, files[].change, convergence.criteria, depends_on
|
||||
- Prefer parallel tasks (minimize depends_on)
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Submit for Approval
|
||||
|
||||
```javascript
|
||||
// Read generated plan
|
||||
const plan = JSON.parse(Read(`${sessionFolder}/plan.json`))
|
||||
|
||||
// Load tasks from .task/ directory (two-layer format)
|
||||
const tasks = plan.task_ids.map(id => JSON.parse(Read(`${sessionFolder}/.task/${id}.json`)))
|
||||
const taskCount = plan.task_count || plan.task_ids.length
|
||||
|
||||
// Send plan summary to coordinator
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator", // team lead
|
||||
content: `## Plan Ready for Review
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Complexity**: ${complexity}
|
||||
**Tasks**: ${taskCount}
|
||||
|
||||
### Task Summary
|
||||
${tasks.map((t, i) => `${i+1}. ${t.title} (${t.scope || 'N/A'})`).join('\n')}
|
||||
|
||||
### Approach
|
||||
${plan.approach}
|
||||
|
||||
### Plan Location
|
||||
${sessionFolder}/plan.json
|
||||
${plan.task_ids ? `Task Files: ${sessionFolder}/.task/` : ''}
|
||||
|
||||
Please review and approve or request revisions.`,
|
||||
summary: `Plan ready: ${taskCount} tasks`
|
||||
})
|
||||
|
||||
// Wait for coordinator response
|
||||
// If approved → mark task completed
|
||||
// If revision requested → update plan based on feedback → resubmit
|
||||
```
|
||||
|
||||
### Phase 5: After Approval
|
||||
|
||||
```javascript
|
||||
// Mark PLAN task as completed
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for more PLAN tasks
|
||||
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 PLAN task → back to Phase 1
|
||||
} else {
|
||||
// No more tasks, idle
|
||||
// Will be woken by coordinator message for new assignments
|
||||
}
|
||||
```
|
||||
|
||||
## Session Files
|
||||
|
||||
```
|
||||
.workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-{angle1}.json # Per-angle exploration results
|
||||
├── exploration-{angle2}.json
|
||||
├── explorations-manifest.json # Exploration index
|
||||
├── planning-context.md # Evidence + understanding (Medium/High)
|
||||
├── plan.json # Plan overview with task_ids[] (NO embedded tasks[])
|
||||
└── .task/ # Independent task files
|
||||
├── TASK-001.json # Task file following task-schema.json
|
||||
├── TASK-002.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| Exploration agent failure | Skip exploration, plan from task description only |
|
||||
| Planning agent failure | Fallback to direct Claude planning |
|
||||
| Plan rejected 3+ times | Notify coordinator, suggest alternative approach |
|
||||
| No PLAN tasks available | Idle, wait for coordinator assignment |
|
||||
| Schema file not found | Use basic plan structure without schema validation |
|
||||
@@ -1,394 +0,0 @@
|
||||
---
|
||||
name: review
|
||||
description: Team reviewer - 代码质量/安全/架构审查、需求验证、发现报告给coordinator
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Review Command (/team:review)
|
||||
|
||||
## Overview
|
||||
|
||||
Team reviewer role command. Operates as a teammate within an Agent Team (typically handled by the tester), responsible for multi-dimensional code review and requirement verification. Reports findings to the coordinator with severity classification.
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (REVIEW-* tasks)
|
||||
- Multi-dimensional review: quality, security, architecture, requirement verification
|
||||
- Pattern-based security scanning with Grep
|
||||
- Acceptance criteria verification against plan
|
||||
- Severity-classified findings (critical/high/medium/low)
|
||||
- Optional CLI-assisted deep analysis (Gemini/Qwen)
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `tester` (same teammate handles both TEST and REVIEW tasks)
|
||||
**Responsibility**: Review code changes → Verify requirements → Report findings
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `review_result` | tester → coordinator | 审查完成 | 附带 verdict(APPROVE/CONDITIONAL/BLOCK)和发现统计 |
|
||||
| `fix_required` | tester → coordinator | 发现 critical issues | 需要创建 IMPL-fix 任务给 executor |
|
||||
| `error` | tester → coordinator | 审查无法完成 | Plan 缺失、变更文件无法读取等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 审查通过
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "review_result", summary: "REVIEW-001: APPROVE, 2 medium + 1 low findings", data: { verdict: "APPROVE", critical: 0, high: 0, medium: 2, low: 1 } })
|
||||
|
||||
// 审查有条件通过
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "review_result", summary: "REVIEW-001: CONDITIONAL, 4 high severity findings需关注", data: { verdict: "CONDITIONAL", critical: 0, high: 4, medium: 3, low: 2 } })
|
||||
|
||||
// 发现 critical 问题
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "fix_required", summary: "发现eval()使用和硬编码密码, 需立即修复", data: { critical: 2, details: ["eval() in auth.ts:42", "hardcoded password in config.ts:15"] } })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "error", summary: "plan.json未找到, 无法进行需求验证" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "tester" --to "coordinator" --type "review_result" --summary "REVIEW-001: APPROVE, 2 medium findings" --data '{"verdict":"APPROVE","critical":0}' --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from tester --to coordinator --type <type> --summary "<text>" [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked REVIEW-* tasks assigned to me
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Review Context Loading
|
||||
├─ Read plan.json (requirements + acceptance criteria)
|
||||
├─ git diff to get changed files
|
||||
├─ Read test results (if available)
|
||||
└─ Read changed file contents
|
||||
|
||||
Phase 3: Multi-Dimensional Review
|
||||
├─ Quality: code style, maintainability, @ts-ignore/any usage
|
||||
├─ Security: eval/exec/innerHTML/hardcoded secrets (Grep patterns)
|
||||
├─ Architecture: layering compliance, modularity, tech debt
|
||||
├─ Requirement Verification: plan acceptance criteria vs implementation
|
||||
└─ Optional: CLI deep analysis (Gemini for security, Qwen for architecture)
|
||||
|
||||
Phase 4: Finding Summary
|
||||
├─ Classify by severity: critical/high/medium/low
|
||||
├─ Generate actionable recommendations
|
||||
└─ Determine overall verdict
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ SendMessage with review findings
|
||||
├─ No critical issues → mark REVIEW task completed
|
||||
└─ Critical issues → flag for immediate attention
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find my assigned REVIEW tasks
|
||||
const tasks = TaskList()
|
||||
const myReviewTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('REVIEW-') &&
|
||||
t.owner === 'tester' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (myReviewTasks.length === 0) return // idle
|
||||
|
||||
const task = TaskGet({ taskId: myReviewTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
```
|
||||
|
||||
### Phase 2: Review Context Loading
|
||||
|
||||
```javascript
|
||||
// Load plan for acceptance criteria
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
let plan = null
|
||||
if (planPathMatch) {
|
||||
try { plan = JSON.parse(Read(planPathMatch[0])) } catch {}
|
||||
}
|
||||
|
||||
// Get changed files via git
|
||||
const changedFiles = Bash(`git diff --name-only HEAD~1 2>/dev/null || git diff --name-only --cached`)
|
||||
.split('\n')
|
||||
.filter(f => f.trim() && !f.startsWith('.'))
|
||||
|
||||
// Read changed file contents for review
|
||||
const fileContents = {}
|
||||
for (const file of changedFiles.slice(0, 20)) { // limit to 20 files
|
||||
try { fileContents[file] = Read(file) } catch {}
|
||||
}
|
||||
|
||||
// Load test results if available
|
||||
let testResults = null
|
||||
const testSummary = tasks.find(t => t.subject.startsWith('TEST-') && t.status === 'completed')
|
||||
```
|
||||
|
||||
### Phase 3: Multi-Dimensional Review
|
||||
|
||||
```javascript
|
||||
const findings = {
|
||||
critical: [],
|
||||
high: [],
|
||||
medium: [],
|
||||
low: []
|
||||
}
|
||||
|
||||
// --- Quality Review ---
|
||||
function reviewQuality(files) {
|
||||
const issues = []
|
||||
|
||||
// Check for @ts-ignore, @ts-expect-error, any type
|
||||
const tsIgnore = Grep({ pattern: '@ts-ignore|@ts-expect-error', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (tsIgnore) issues.push({ type: 'quality', detail: '@ts-ignore/@ts-expect-error usage detected', severity: 'medium' })
|
||||
|
||||
const anyType = Grep({ pattern: ': any[^A-Z]|as any', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (anyType) issues.push({ type: 'quality', detail: 'Untyped `any` usage detected', severity: 'medium' })
|
||||
|
||||
// Check for console.log left in production code
|
||||
const consoleLogs = Grep({ pattern: 'console\\.log', glob: '*.{ts,tsx,js,jsx}', path: 'src/', output_mode: 'content' })
|
||||
if (consoleLogs) issues.push({ type: 'quality', detail: 'console.log found in source code', severity: 'low' })
|
||||
|
||||
// Check for empty catch blocks
|
||||
const emptyCatch = Grep({ pattern: 'catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content', multiline: true })
|
||||
if (emptyCatch) issues.push({ type: 'quality', detail: 'Empty catch blocks detected', severity: 'high' })
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
// --- Security Review ---
|
||||
function reviewSecurity(files) {
|
||||
const issues = []
|
||||
|
||||
// Dangerous functions
|
||||
const dangerousFns = Grep({ pattern: '\\beval\\b|\\bexec\\b|innerHTML|dangerouslySetInnerHTML', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (dangerousFns) issues.push({ type: 'security', detail: 'Dangerous function usage: eval/exec/innerHTML', severity: 'critical' })
|
||||
|
||||
// Hardcoded secrets
|
||||
const secrets = Grep({ pattern: 'password\\s*=\\s*["\']|secret\\s*=\\s*["\']|api_key\\s*=\\s*["\']', glob: '*.{ts,tsx,js,jsx,py}', output_mode: 'content', '-i': true })
|
||||
if (secrets) issues.push({ type: 'security', detail: 'Hardcoded secrets/passwords detected', severity: 'critical' })
|
||||
|
||||
// SQL injection risk
|
||||
const sqlInjection = Grep({ pattern: 'query\\s*\\(\\s*`|execute\\s*\\(\\s*`|\\$\\{.*\\}.*(?:SELECT|INSERT|UPDATE|DELETE)', glob: '*.{ts,js,py}', output_mode: 'content', '-i': true })
|
||||
if (sqlInjection) issues.push({ type: 'security', detail: 'Potential SQL injection via template literals', severity: 'critical' })
|
||||
|
||||
// XSS via user input
|
||||
const xssRisk = Grep({ pattern: 'document\\.write|window\\.location\\s*=', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (xssRisk) issues.push({ type: 'security', detail: 'Potential XSS vectors detected', severity: 'high' })
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
// --- Architecture Review ---
|
||||
function reviewArchitecture(files) {
|
||||
const issues = []
|
||||
|
||||
// Circular dependency indicators
|
||||
// Check for imports that may create cycles
|
||||
for (const [file, content] of Object.entries(fileContents)) {
|
||||
const imports = content.match(/from\s+['"]([^'"]+)['"]/g) || []
|
||||
// Basic heuristic: component importing from parent directory
|
||||
const parentImports = imports.filter(i => i.includes('../..'))
|
||||
if (parentImports.length > 2) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: excessive parent directory imports (possible layering violation)`, severity: 'medium' })
|
||||
}
|
||||
}
|
||||
|
||||
// Large file detection
|
||||
for (const [file, content] of Object.entries(fileContents)) {
|
||||
const lines = content.split('\n').length
|
||||
if (lines > 500) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: ${lines} lines - consider splitting`, severity: 'low' })
|
||||
}
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
// --- Requirement Verification ---
|
||||
function verifyRequirements(plan) {
|
||||
const issues = []
|
||||
if (!plan) {
|
||||
issues.push({ type: 'requirement', detail: 'No plan found for requirement verification', severity: 'medium' })
|
||||
return issues
|
||||
}
|
||||
|
||||
for (const planTask of plan.tasks) {
|
||||
for (const criterion of (planTask.acceptance || [])) {
|
||||
// Check if criterion appears to be met
|
||||
// This is a heuristic check - look for relevant code in changed files
|
||||
const keywords = criterion.toLowerCase().split(/\s+/).filter(w => w.length > 4)
|
||||
const hasEvidence = keywords.some(kw =>
|
||||
Object.values(fileContents).some(content => content.toLowerCase().includes(kw))
|
||||
)
|
||||
|
||||
if (!hasEvidence) {
|
||||
issues.push({
|
||||
type: 'requirement',
|
||||
detail: `Acceptance criterion may not be met: "${criterion}" (task: ${planTask.title})`,
|
||||
severity: 'high'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
// Execute all review dimensions
|
||||
const qualityIssues = reviewQuality(changedFiles)
|
||||
const securityIssues = reviewSecurity(changedFiles)
|
||||
const architectureIssues = reviewArchitecture(changedFiles)
|
||||
const requirementIssues = plan ? verifyRequirements(plan) : []
|
||||
|
||||
// Classify into severity buckets
|
||||
const allIssues = [...qualityIssues, ...securityIssues, ...architectureIssues, ...requirementIssues]
|
||||
allIssues.forEach(issue => {
|
||||
findings[issue.severity].push(issue)
|
||||
})
|
||||
```
|
||||
|
||||
### Phase 4: Finding Summary
|
||||
|
||||
```javascript
|
||||
const totalIssues = Object.values(findings).flat().length
|
||||
const hasCritical = findings.critical.length > 0
|
||||
|
||||
const verdict = hasCritical
|
||||
? 'BLOCK - Critical issues must be resolved'
|
||||
: findings.high.length > 3
|
||||
? 'CONDITIONAL - High severity issues should be addressed'
|
||||
: 'APPROVE - No blocking issues found'
|
||||
|
||||
const recommendations = []
|
||||
if (hasCritical) {
|
||||
recommendations.push('Fix all critical security issues before merging')
|
||||
}
|
||||
if (findings.high.length > 0) {
|
||||
recommendations.push('Address high severity issues in a follow-up')
|
||||
}
|
||||
if (findings.medium.length > 3) {
|
||||
recommendations.push('Consider refactoring to reduce medium severity issues')
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Code Review Report
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Verdict**: ${verdict}
|
||||
**Files Reviewed**: ${changedFiles.length}
|
||||
**Total Findings**: ${totalIssues}
|
||||
|
||||
### Finding Summary
|
||||
- Critical: ${findings.critical.length}
|
||||
- High: ${findings.high.length}
|
||||
- Medium: ${findings.medium.length}
|
||||
- Low: ${findings.low.length}
|
||||
|
||||
${findings.critical.length > 0 ? `### Critical Issues
|
||||
${findings.critical.map(f => `- [${f.type.toUpperCase()}] ${f.detail}`).join('\n')}
|
||||
` : ''}
|
||||
${findings.high.length > 0 ? `### High Severity
|
||||
${findings.high.map(f => `- [${f.type.toUpperCase()}] ${f.detail}`).join('\n')}
|
||||
` : ''}
|
||||
${findings.medium.length > 0 ? `### Medium Severity
|
||||
${findings.medium.map(f => `- [${f.type.toUpperCase()}] ${f.detail}`).join('\n')}
|
||||
` : ''}
|
||||
### Recommendations
|
||||
${recommendations.map(r => `- ${r}`).join('\n')}
|
||||
|
||||
${plan ? `### Requirement Verification
|
||||
${plan.tasks.map(t => `- **${t.title}**: ${requirementIssues.filter(i => i.detail.includes(t.title)).length === 0 ? 'Criteria met' : 'Needs verification'}`).join('\n')}
|
||||
` : ''}`,
|
||||
summary: `Review: ${verdict.split(' - ')[0]} (${totalIssues} findings)`
|
||||
})
|
||||
|
||||
// Mark task based on verdict
|
||||
if (!hasCritical) {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Keep in_progress, coordinator needs to create fix tasks
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `Critical issues found in review. Recommend creating IMPL-fix tasks for executor to address: ${findings.critical.map(f => f.detail).join('; ')}`,
|
||||
summary: "Critical issues need fix tasks"
|
||||
})
|
||||
}
|
||||
|
||||
// Check for next REVIEW task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('REVIEW-') &&
|
||||
t.owner === 'tester' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task
|
||||
}
|
||||
```
|
||||
|
||||
## Optional: CLI Deep Analysis
|
||||
|
||||
For complex reviews, the tester can invoke CLI tools for deeper analysis:
|
||||
|
||||
```bash
|
||||
# Security deep analysis (Gemini)
|
||||
ccw cli -p "
|
||||
PURPOSE: Deep security audit of implementation changes
|
||||
TASK: Scan for OWASP Top 10 vulnerabilities, injection flaws, auth bypass vectors
|
||||
CONTEXT: @src/**/*.{ts,tsx,js,jsx}
|
||||
EXPECTED: Security findings with severity, file:line references, remediation
|
||||
CONSTRAINTS: Focus on changed files only
|
||||
" --tool gemini --mode analysis
|
||||
|
||||
# Architecture deep analysis (Qwen)
|
||||
ccw cli -p "
|
||||
PURPOSE: Architecture compliance review
|
||||
TASK: Evaluate layering, modularity, separation of concerns
|
||||
CONTEXT: @src/**/*
|
||||
EXPECTED: Architecture assessment with recommendations
|
||||
CONSTRAINTS: Focus on changed modules
|
||||
" --tool qwen --mode analysis
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| Plan file not found | Review without requirement verification, note in report |
|
||||
| No changed files detected | Report to coordinator, may need manual file list |
|
||||
| Grep pattern errors | Skip specific check, continue with remaining |
|
||||
| CLI analysis timeout | Report partial results, note incomplete analysis |
|
||||
| Too many files to review (> 50) | Focus on source files, skip generated/vendor files |
|
||||
| Cannot determine file content | Skip file, note in report |
|
||||
@@ -1,321 +0,0 @@
|
||||
---
|
||||
name: spec-analyst
|
||||
description: Team spec analyst - 种子分析、代码库探索、上下文收集、多维度研究
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Spec Analyst Command (/team:spec-analyst)
|
||||
|
||||
## Overview
|
||||
|
||||
Team spec-analyst role command. Operates as a teammate within a Spec Team, responsible for discovery, codebase exploration, and multi-dimensional context gathering. Maps to spec-generator Phase 1 (Discovery).
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (RESEARCH-* tasks)
|
||||
- Seed analysis: problem statement, users, domain, constraints extraction
|
||||
- Codebase exploration: existing patterns, architecture, tech stack detection
|
||||
- Multi-dimensional research: 3-5 exploration dimensions with complexity assessment
|
||||
- Structured context output for downstream discussion and drafting
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `spec-analyst`
|
||||
**Responsibility**: Seed Analysis → Codebase Exploration → Context Packaging → Report
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `research_ready` | spec-analyst → coordinator | 研究完成 | 附带 discovery-context.json 路径和维度摘要 |
|
||||
| `research_progress` | spec-analyst → coordinator | 长时间研究进展 | 阶段性进展更新 |
|
||||
| `error` | spec-analyst → coordinator | 遇到不可恢复错误 | 代码库访问失败、CLI 超时等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 研究就绪
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "research_ready", summary: "研究完成: 5个探索维度, 检测到React+Node技术栈", ref: ".workflow/.spec-team/session/discovery-context.json" })
|
||||
|
||||
// 进展更新
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "research_progress", summary: "种子分析完成, 开始代码库探索 (2/3)" })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "error", summary: "代码库探索失败: 项目根目录无法识别" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "spec-analyst" --to "coordinator" --type "research_ready" --summary "研究完成: 5个探索维度" --ref "${sessionFolder}/discovery-context.json" --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from spec-analyst --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked RESEARCH-* tasks
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Seed Analysis
|
||||
├─ Parse topic/idea from task description
|
||||
├─ Extract: problem statement, target users, domain, constraints
|
||||
├─ Identify 3-5 exploration dimensions
|
||||
└─ Assess complexity (simple/moderate/complex)
|
||||
|
||||
Phase 3: Codebase Exploration (conditional)
|
||||
├─ Detect project presence (package.json, Cargo.toml, etc.)
|
||||
├─ Explore architecture patterns and conventions
|
||||
├─ Map technology stack and dependencies
|
||||
└─ Identify integration constraints
|
||||
|
||||
Phase 4: Context Packaging
|
||||
├─ Generate spec-config.json (session state)
|
||||
├─ Generate discovery-context.json (research results)
|
||||
└─ Validate output completeness
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ team_msg log + SendMessage research summary
|
||||
├─ TaskUpdate completed
|
||||
└─ Check for next RESEARCH-* task
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find assigned RESEARCH-* tasks
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('RESEARCH-') &&
|
||||
t.owner === 'spec-analyst' &&
|
||||
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: Seed Analysis
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : '.workflow/.spec-team/default'
|
||||
|
||||
// Parse topic from task description
|
||||
const topicLines = task.description.split('\n').filter(l => !l.startsWith('Session:') && !l.startsWith('输出:') && l.trim())
|
||||
const topic = topicLines[0] || task.subject.replace('RESEARCH-001: ', '')
|
||||
|
||||
// Use Gemini CLI for seed analysis
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Analyze the following topic/idea and extract structured seed information for specification generation.
|
||||
TASK:
|
||||
• Extract problem statement (what problem does this solve)
|
||||
• Identify target users and their pain points
|
||||
• Determine domain and industry context
|
||||
• List constraints and assumptions
|
||||
• Identify 3-5 exploration dimensions for deeper research
|
||||
• Assess complexity (simple/moderate/complex)
|
||||
|
||||
TOPIC: ${topic}
|
||||
|
||||
MODE: analysis
|
||||
CONTEXT: @**/*
|
||||
EXPECTED: JSON output with fields: problem_statement, target_users[], domain, constraints[], exploration_dimensions[], complexity_assessment
|
||||
CONSTRAINTS: Output as valid JSON" --tool gemini --mode analysis --rule analysis-analyze-technical-document`,
|
||||
run_in_background: true
|
||||
})
|
||||
// Wait for CLI result
|
||||
|
||||
// Parse Gemini analysis result
|
||||
const seedAnalysis = parseCLIResult(geminiOutput)
|
||||
```
|
||||
|
||||
### Phase 3: Codebase Exploration (conditional)
|
||||
|
||||
```javascript
|
||||
// Check if there's an existing codebase to explore
|
||||
const hasProject = Bash(`test -f package.json || test -f Cargo.toml || test -f pyproject.toml || test -f go.mod; echo $?`)
|
||||
|
||||
if (hasProject === '0') {
|
||||
// Progress update
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "research_progress", summary: "种子分析完成, 开始代码库探索" })
|
||||
|
||||
// Explore codebase using ACE search
|
||||
const archSearch = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: `Architecture patterns, main modules, entry points for: ${topic}`
|
||||
})
|
||||
|
||||
// Detect tech stack
|
||||
const techStack = {
|
||||
languages: [],
|
||||
frameworks: [],
|
||||
databases: [],
|
||||
infrastructure: []
|
||||
}
|
||||
|
||||
// Scan package files for dependencies
|
||||
const pkgJson = Read('package.json') // if exists
|
||||
// Parse and categorize dependencies
|
||||
|
||||
// Explore existing patterns
|
||||
const patterns = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: `Similar features, existing conventions, coding patterns related to: ${topic}`
|
||||
})
|
||||
|
||||
// Integration constraints
|
||||
const integrationPoints = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: `API endpoints, service boundaries, module interfaces that ${topic} would integrate with`
|
||||
})
|
||||
|
||||
var codebaseContext = {
|
||||
tech_stack: techStack,
|
||||
architecture_patterns: archSearch,
|
||||
existing_conventions: patterns,
|
||||
integration_points: integrationPoints,
|
||||
constraints_from_codebase: []
|
||||
}
|
||||
} else {
|
||||
var codebaseContext = null
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Context Packaging
|
||||
|
||||
```javascript
|
||||
// Generate spec-config.json
|
||||
const specConfig = {
|
||||
session_id: `SPEC-${topicSlug}-${dateStr}`,
|
||||
topic: topic,
|
||||
status: "research_complete",
|
||||
complexity: seedAnalysis.complexity_assessment || "moderate",
|
||||
phases_completed: ["discovery"],
|
||||
created_at: new Date().toISOString(),
|
||||
session_folder: sessionFolder,
|
||||
discussion_depth: task.description.match(/讨论深度:\s*(.+)/)?.[1] || "standard"
|
||||
}
|
||||
Write(`${sessionFolder}/spec-config.json`, JSON.stringify(specConfig, null, 2))
|
||||
|
||||
// Generate discovery-context.json
|
||||
const discoveryContext = {
|
||||
session_id: specConfig.session_id,
|
||||
phase: 1,
|
||||
document_type: "discovery-context",
|
||||
status: "complete",
|
||||
generated_at: new Date().toISOString(),
|
||||
seed_analysis: {
|
||||
problem_statement: seedAnalysis.problem_statement,
|
||||
target_users: seedAnalysis.target_users,
|
||||
domain: seedAnalysis.domain,
|
||||
constraints: seedAnalysis.constraints,
|
||||
exploration_dimensions: seedAnalysis.exploration_dimensions,
|
||||
complexity: seedAnalysis.complexity_assessment
|
||||
},
|
||||
codebase_context: codebaseContext,
|
||||
recommendations: {
|
||||
focus_areas: seedAnalysis.exploration_dimensions?.slice(0, 3) || [],
|
||||
risks: [],
|
||||
open_questions: []
|
||||
}
|
||||
}
|
||||
Write(`${sessionFolder}/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const dimensionCount = discoveryContext.seed_analysis.exploration_dimensions?.length || 0
|
||||
const hasCodebase = codebaseContext !== null
|
||||
|
||||
// Log before SendMessage
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "spec-analyst", to: "coordinator",
|
||||
type: "research_ready",
|
||||
summary: `研究完成: ${dimensionCount}个探索维度, ${hasCodebase ? '有' : '无'}代码库上下文, 复杂度=${specConfig.complexity}`,
|
||||
ref: `${sessionFolder}/discovery-context.json`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 研究分析结果
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**复杂度**: ${specConfig.complexity}
|
||||
**代码库**: ${hasCodebase ? '已检测到现有项目' : '全新项目(无现有代码)'}
|
||||
|
||||
### 问题陈述
|
||||
${discoveryContext.seed_analysis.problem_statement}
|
||||
|
||||
### 目标用户
|
||||
${(discoveryContext.seed_analysis.target_users || []).map(u => `- ${u}`).join('\n')}
|
||||
|
||||
### 探索维度
|
||||
${(discoveryContext.seed_analysis.exploration_dimensions || []).map((d, i) => `${i+1}. ${d}`).join('\n')}
|
||||
|
||||
### 约束条件
|
||||
${(discoveryContext.seed_analysis.constraints || []).map(c => `- ${c}`).join('\n')}
|
||||
|
||||
${hasCodebase ? `### 代码库上下文
|
||||
- 技术栈: ${JSON.stringify(codebaseContext.tech_stack)}
|
||||
- 集成点: ${codebaseContext.integration_points?.length || 0}个` : ''}
|
||||
|
||||
### 输出位置
|
||||
- Config: ${sessionFolder}/spec-config.json
|
||||
- Context: ${sessionFolder}/discovery-context.json
|
||||
|
||||
研究已就绪,可进入讨论轮次 DISCUSS-001。`,
|
||||
summary: `研究就绪: ${dimensionCount}维度, ${specConfig.complexity}`
|
||||
})
|
||||
|
||||
// Mark task completed
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next RESEARCH task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('RESEARCH-') &&
|
||||
t.owner === 'spec-analyst' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task -> back to Phase 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No RESEARCH-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Gemini CLI analysis failure | Fallback to direct Claude analysis without CLI |
|
||||
| Codebase detection failed | Continue as new project (no codebase context) |
|
||||
| Session folder cannot be created | Notify coordinator, request alternative path |
|
||||
| Topic too vague for analysis | Report to coordinator with clarification questions |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
@@ -1,352 +0,0 @@
|
||||
---
|
||||
name: spec-coordinate
|
||||
description: Team spec coordinator - 规格文档工作流编排、讨论轮次管理、跨阶段共识推进
|
||||
argument-hint: "[--team-name=NAME] \"spec topic description\""
|
||||
allowed-tools: TeamCreate(*), TeamDelete(*), SendMessage(*), TaskCreate(*), TaskUpdate(*), TaskList(*), TaskGet(*), Task(*), AskUserQuestion(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Grep(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Spec Coordinate Command (/team:spec-coordinate)
|
||||
|
||||
规格文档团队协调器。需求发现 → 研究分析 → 讨论共识 → 文档撰写 → 质量审查 → 最终交付。每个阶段之间穿插结构化讨论轮次,确保多角度审视和团队共识。
|
||||
|
||||
## 消息总线
|
||||
|
||||
所有 teammate 在 SendMessage 的**同时**必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
// 记录消息(每个 teammate 发 SendMessage 前调用)
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-analyst", to: "coordinator", type: "research_ready", summary: "研究完成: 5个探索维度", ref: ".workflow/.spec-team/session/discovery-context.json" })
|
||||
|
||||
// 查看全部消息
|
||||
mcp__ccw-tools__team_msg({ operation: "list", team: teamName })
|
||||
|
||||
// 按角色过滤
|
||||
mcp__ccw-tools__team_msg({ operation: "list", team: teamName, from: "spec-discuss", last: 5 })
|
||||
|
||||
// 查看团队状态
|
||||
mcp__ccw-tools__team_msg({ operation: "status", team: teamName })
|
||||
```
|
||||
|
||||
**日志位置**: `.workflow/.team-msg/{team-name}/messages.jsonl`
|
||||
**消息类型**: `research_ready | research_progress | draft_ready | draft_revision | quality_result | discussion_ready | discussion_blocked | fix_required | error | shutdown`
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
// log
|
||||
Bash(`ccw team log --team "${teamName}" --from "coordinator" --to "spec-analyst" --type "plan_approved" --summary "研究结果已确认" --json`)
|
||||
// list
|
||||
Bash(`ccw team list --team "${teamName}" --last 10 --json`)
|
||||
// list (带过滤)
|
||||
Bash(`ccw team list --team "${teamName}" --from "spec-discuss" --last 5 --json`)
|
||||
// status
|
||||
Bash(`ccw team status --team "${teamName}" --json`)
|
||||
// read
|
||||
Bash(`ccw team read --team "${teamName}" --id "MSG-003" --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team <operation> --team <team> [--from/--to/--type/--summary/--ref/--data/--id/--last] [--json]`
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Topic → [RESEARCH: spec-analyst] → [DISCUSS-001: 范围讨论]
|
||||
→ [DRAFT-001: Product Brief] → [DISCUSS-002: 多视角评审]
|
||||
→ [DRAFT-002: Requirements/PRD] → [DISCUSS-003: 需求完整性讨论]
|
||||
→ [DRAFT-003: Architecture] → [DISCUSS-004: 技术可行性讨论]
|
||||
→ [DRAFT-004: Epics & Stories] → [DISCUSS-005: 执行就绪讨论]
|
||||
→ [QUALITY-001: Readiness Check] → [DISCUSS-006: 最终签收]
|
||||
→ 交付 → 等待新需求/关闭
|
||||
```
|
||||
|
||||
## 讨论轮次设计
|
||||
|
||||
每个讨论轮次由 spec-discuss 角色执行,包含以下维度:
|
||||
|
||||
| 讨论轮次 | 发生时机 | 讨论焦点 | 输入制品 |
|
||||
|----------|----------|----------|----------|
|
||||
| DISCUSS-001 | 研究完成后 | 范围确认、方向调整、风险预判 | discovery-context.json |
|
||||
| DISCUSS-002 | Product Brief 后 | 产品定位、用户画像、竞品分析 | product-brief.md |
|
||||
| DISCUSS-003 | PRD 后 | 需求完整性、优先级、可测试性 | requirements/_index.md |
|
||||
| DISCUSS-004 | Architecture 后 | 技术选型、可扩展性、安全性 | architecture/_index.md |
|
||||
| DISCUSS-005 | Epics 后 | 执行顺序、MVP范围、估算合理性 | epics/_index.md |
|
||||
| DISCUSS-006 | Quality Check 后 | 最终交付确认、遗留问题、下一步 | readiness-report.md |
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 1: 需求解析
|
||||
|
||||
解析 `$ARGUMENTS` 获取 `--team-name` 和规格主题。使用 AskUserQuestion 收集:
|
||||
- 规格范围(MVP / 完整 / 企业级)
|
||||
- 重点领域(产品定义 / 技术架构 / 全面规格)
|
||||
- 讨论深度(快速共识 / 深度讨论 / 全面辩论)
|
||||
|
||||
简单主题可跳过澄清。
|
||||
|
||||
### Phase 2: 创建 Team + Spawn 4 Teammates
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Session setup
|
||||
const topicSlug = topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const sessionFolder = `.workflow/.spec-team/${topicSlug}-${dateStr}`
|
||||
Bash(`mkdir -p ${sessionFolder}`)
|
||||
```
|
||||
|
||||
**Spawn 时只传角色和需求,工作细节由 skill 定义**:
|
||||
|
||||
```javascript
|
||||
// Spec Analyst (研究分析)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "spec-analyst",
|
||||
prompt: `你是 team "${teamName}" 的 SPEC ANALYST。
|
||||
|
||||
当你收到 RESEARCH-* 任务时,调用 Skill(skill="team:spec-analyst") 执行研究分析。
|
||||
|
||||
当前主题: ${topicDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "spec-analyst", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到分配给你的 RESEARCH-* 任务
|
||||
2. Skill(skill="team:spec-analyst") 执行发现和研究
|
||||
3. team_msg log + SendMessage 将研究结果发给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个 RESEARCH 任务`
|
||||
})
|
||||
|
||||
// Spec Writer (文档撰写)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "spec-writer",
|
||||
prompt: `你是 team "${teamName}" 的 SPEC WRITER。
|
||||
|
||||
当你收到 DRAFT-* 任务时,调用 Skill(skill="team:spec-writer") 执行文档撰写。
|
||||
|
||||
当前主题: ${topicDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "spec-writer", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到未阻塞的 DRAFT-* 任务
|
||||
2. Skill(skill="team:spec-writer") 执行文档生成
|
||||
3. team_msg log + SendMessage 报告文档就绪
|
||||
4. TaskUpdate completed → 检查下一个 DRAFT 任务`
|
||||
})
|
||||
|
||||
// Spec Reviewer (质量审查)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "spec-reviewer",
|
||||
prompt: `你是 team "${teamName}" 的 SPEC REVIEWER。
|
||||
|
||||
当你收到 QUALITY-* 任务时,调用 Skill(skill="team:spec-reviewer") 执行质量审查。
|
||||
|
||||
当前主题: ${topicDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "spec-reviewer", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到未阻塞的 QUALITY-* 任务
|
||||
2. Skill(skill="team:spec-reviewer") 执行质量验证
|
||||
3. team_msg log + SendMessage 报告审查结果
|
||||
4. TaskUpdate completed → 检查下一个 QUALITY 任务`
|
||||
})
|
||||
|
||||
// Spec Discuss (讨论促进者)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "spec-discuss",
|
||||
prompt: `你是 team "${teamName}" 的 SPEC DISCUSS FACILITATOR。
|
||||
|
||||
当你收到 DISCUSS-* 任务时,调用 Skill(skill="team:spec-discuss") 执行结构化团队讨论。
|
||||
|
||||
当前主题: ${topicDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
讨论深度: ${discussionDepth}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录:
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: "${teamName}", from: "spec-discuss", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到未阻塞的 DISCUSS-* 任务
|
||||
2. Skill(skill="team:spec-discuss") 执行结构化讨论
|
||||
3. team_msg log + SendMessage 报告讨论共识
|
||||
4. TaskUpdate completed → 检查下一个 DISCUSS 任务`
|
||||
})
|
||||
```
|
||||
|
||||
### Phase 3: 创建完整任务链
|
||||
|
||||
```javascript
|
||||
// ===== RESEARCH Phase =====
|
||||
TaskCreate({ subject: "RESEARCH-001: 主题发现与上下文研究", description: `${topicDescription}\n\nSession: ${sessionFolder}\n输出: ${sessionFolder}/spec-config.json + discovery-context.json`, activeForm: "研究中" })
|
||||
TaskUpdate({ taskId: researchId, owner: "spec-analyst" })
|
||||
|
||||
// ===== DISCUSS Round 1: 范围讨论 =====
|
||||
TaskCreate({ subject: "DISCUSS-001: 研究结果讨论 - 范围确认与方向调整", description: `讨论 RESEARCH-001 的发现结果\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/discovery-context.json\n输出: ${sessionFolder}/discussions/discuss-001-scope.md\n\n讨论维度: 范围确认、方向调整、风险预判、探索缺口`, activeForm: "讨论范围中" })
|
||||
TaskUpdate({ taskId: discuss1Id, owner: "spec-discuss", addBlockedBy: [researchId] })
|
||||
|
||||
// ===== DRAFT Phase 1: Product Brief =====
|
||||
TaskCreate({ subject: "DRAFT-001: 撰写 Product Brief", description: `基于研究和讨论共识撰写产品简报\n\nSession: ${sessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${sessionFolder}/product-brief.md\n\n使用多视角分析: 产品/技术/用户`, activeForm: "撰写 Brief 中" })
|
||||
TaskUpdate({ taskId: draft1Id, owner: "spec-writer", addBlockedBy: [discuss1Id] })
|
||||
|
||||
// ===== DISCUSS Round 2: Brief 评审 =====
|
||||
TaskCreate({ subject: "DISCUSS-002: Product Brief 多视角评审", description: `评审 Product Brief 文档\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/product-brief.md\n输出: ${sessionFolder}/discussions/discuss-002-brief.md\n\n讨论维度: 产品定位、目标用户、成功指标、竞品差异`, activeForm: "评审 Brief 中" })
|
||||
TaskUpdate({ taskId: discuss2Id, owner: "spec-discuss", addBlockedBy: [draft1Id] })
|
||||
|
||||
// ===== DRAFT Phase 2: Requirements/PRD =====
|
||||
TaskCreate({ subject: "DRAFT-002: 撰写 Requirements/PRD", description: `基于 Brief 和讨论反馈撰写需求文档\n\nSession: ${sessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${sessionFolder}/requirements/\n\n包含: 功能需求(REQ-*) + 非功能需求(NFR-*) + MoSCoW 优先级`, activeForm: "撰写 PRD 中" })
|
||||
TaskUpdate({ taskId: draft2Id, owner: "spec-writer", addBlockedBy: [discuss2Id] })
|
||||
|
||||
// ===== DISCUSS Round 3: 需求完整性 =====
|
||||
TaskCreate({ subject: "DISCUSS-003: 需求完整性与优先级讨论", description: `讨论 PRD 需求完整性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/requirements/_index.md\n输出: ${sessionFolder}/discussions/discuss-003-requirements.md\n\n讨论维度: 需求遗漏、MoSCoW合理性、验收标准可测性、非功能需求充分性`, activeForm: "讨论需求中" })
|
||||
TaskUpdate({ taskId: discuss3Id, owner: "spec-discuss", addBlockedBy: [draft2Id] })
|
||||
|
||||
// ===== DRAFT Phase 3: Architecture =====
|
||||
TaskCreate({ subject: "DRAFT-003: 撰写 Architecture Document", description: `基于需求和讨论反馈撰写架构文档\n\nSession: ${sessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${sessionFolder}/architecture/\n\n包含: 架构风格 + 组件图 + 技术选型 + ADR-* + 数据模型`, activeForm: "撰写架构中" })
|
||||
TaskUpdate({ taskId: draft3Id, owner: "spec-writer", addBlockedBy: [discuss3Id] })
|
||||
|
||||
// ===== DISCUSS Round 4: 技术可行性 =====
|
||||
TaskCreate({ subject: "DISCUSS-004: 架构决策与技术可行性讨论", description: `讨论架构设计合理性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/architecture/_index.md\n输出: ${sessionFolder}/discussions/discuss-004-architecture.md\n\n讨论维度: 技术选型风险、可扩展性、安全架构、ADR替代方案`, activeForm: "讨论架构中" })
|
||||
TaskUpdate({ taskId: discuss4Id, owner: "spec-discuss", addBlockedBy: [draft3Id] })
|
||||
|
||||
// ===== DRAFT Phase 4: Epics & Stories =====
|
||||
TaskCreate({ subject: "DRAFT-004: 撰写 Epics & Stories", description: `基于架构和讨论反馈撰写史诗和用户故事\n\nSession: ${sessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${sessionFolder}/epics/\n\n包含: EPIC-* + STORY-* + 依赖图 + MVP定义 + 执行顺序`, activeForm: "撰写 Epics 中" })
|
||||
TaskUpdate({ taskId: draft4Id, owner: "spec-writer", addBlockedBy: [discuss4Id] })
|
||||
|
||||
// ===== DISCUSS Round 5: 执行就绪 =====
|
||||
TaskCreate({ subject: "DISCUSS-005: 执行计划与MVP范围讨论", description: `讨论执行计划就绪性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/epics/_index.md\n输出: ${sessionFolder}/discussions/discuss-005-epics.md\n\n讨论维度: Epic粒度、故事估算、MVP范围、执行顺序、依赖风险`, activeForm: "讨论执行计划中" })
|
||||
TaskUpdate({ taskId: discuss5Id, owner: "spec-discuss", addBlockedBy: [draft4Id] })
|
||||
|
||||
// ===== QUALITY: Readiness Check =====
|
||||
TaskCreate({ subject: "QUALITY-001: 规格就绪度检查", description: `全文档交叉验证和质量评分\n\nSession: ${sessionFolder}\n输入: 全部文档\n输出: ${sessionFolder}/readiness-report.md + spec-summary.md\n\n评分维度: 完整性(25%) + 一致性(25%) + 可追溯性(25%) + 深度(25%)`, activeForm: "质量检查中" })
|
||||
TaskUpdate({ taskId: qualityId, owner: "spec-reviewer", addBlockedBy: [discuss5Id] })
|
||||
|
||||
// ===== DISCUSS Round 6: 最终签收 =====
|
||||
TaskCreate({ subject: "DISCUSS-006: 最终签收与交付确认", description: `最终讨论和签收\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/readiness-report.md\n输出: ${sessionFolder}/discussions/discuss-006-final.md\n\n讨论维度: 质量报告审查、遗留问题处理、交付确认、下一步建议`, activeForm: "最终签收讨论中" })
|
||||
TaskUpdate({ taskId: discuss6Id, owner: "spec-discuss", addBlockedBy: [qualityId] })
|
||||
```
|
||||
|
||||
### Phase 4: 协调主循环
|
||||
|
||||
接收 teammate 消息,根据内容做调度决策。**每次做出决策前先 `team_msg list` 查看最近消息,每次做出决策后 `team_msg log` 记录**:
|
||||
|
||||
| 收到消息 | 操作 |
|
||||
|----------|------|
|
||||
| Analyst: 研究就绪 | 读取 discovery-context.json → team_msg log → 解锁 DISCUSS-001 |
|
||||
| Discuss: 讨论共识达成 | 读取 discussion.md → 判断是否需要修订 → 解锁下一个 DRAFT 任务 |
|
||||
| Discuss: 讨论阻塞 | 介入讨论 → AskUserQuestion 获取用户决策 → 手动推进 |
|
||||
| Writer: 文档就绪 | 读取文档摘要 → team_msg log → 解锁对应 DISCUSS 任务 |
|
||||
| Writer: 文档修订 | 更新依赖 → 解锁相关讨论任务 |
|
||||
| Reviewer: 质量通过 (≥80%) | team_msg log → 解锁 DISCUSS-006 |
|
||||
| Reviewer: 质量需审查 (60-79%) | team_msg log + 通知 writer 改进建议 |
|
||||
| Reviewer: 质量失败 (<60%) | 创建 DRAFT-fix 任务 → 分配 writer |
|
||||
| 所有任务 completed | → Phase 5 |
|
||||
|
||||
**讨论阻塞处理**:
|
||||
```javascript
|
||||
// 当 DISCUSS 任务报告阻塞(无法达成共识)
|
||||
// Coordinator 介入
|
||||
if (msgType === 'discussion_blocked') {
|
||||
const blockReason = msg.data.reason
|
||||
const options = msg.data.options
|
||||
|
||||
// 上报用户做出决策
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: `讨论 ${msg.ref} 遇到分歧: ${blockReason}\n请选择方向:`,
|
||||
header: "Decision",
|
||||
multiSelect: false,
|
||||
options: options.map(opt => ({ label: opt.label, description: opt.description }))
|
||||
}]
|
||||
})
|
||||
|
||||
// 将用户决策写入讨论记录
|
||||
// 解锁后续任务
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: 汇报 + 持久循环
|
||||
|
||||
汇总所有文档、讨论轮次结果、质量评分报告用户。
|
||||
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "规格文档已完成。下一步:",
|
||||
header: "Next",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "交付执行", description: "将规格交给 lite-plan/req-plan/plan 执行" },
|
||||
{ label: "新主题", description: "为新主题生成规格(复用团队)" },
|
||||
{ label: "关闭团队", description: "关闭所有 teammate 并清理" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
// 交付执行 → 提示可用的执行 workflow
|
||||
// 新主题 → 回到 Phase 1(复用 team,新建全套任务链)
|
||||
// 关闭 → shutdown 给每个 teammate → TeamDelete()
|
||||
```
|
||||
|
||||
## Session 文件结构
|
||||
|
||||
```
|
||||
.workflow/.spec-team/{topic-slug}-{YYYY-MM-DD}/
|
||||
├── spec-config.json # Session state
|
||||
├── discovery-context.json # Research context (Phase 1)
|
||||
├── product-brief.md # Product Brief (Phase 2)
|
||||
├── requirements/ # PRD (Phase 3)
|
||||
│ ├── _index.md
|
||||
│ ├── REQ-001-*.md
|
||||
│ └── NFR-*-*.md
|
||||
├── architecture/ # Architecture (Phase 4)
|
||||
│ ├── _index.md
|
||||
│ └── ADR-001-*.md
|
||||
├── epics/ # Epics & Stories (Phase 5)
|
||||
│ ├── _index.md
|
||||
│ └── EPIC-001-*.md
|
||||
├── readiness-report.md # Quality validation (Phase 6)
|
||||
├── spec-summary.md # Executive summary
|
||||
└── discussions/ # 讨论记录
|
||||
├── discuss-001-scope.md
|
||||
├── discuss-002-brief.md
|
||||
├── discuss-003-requirements.md
|
||||
├── discuss-004-architecture.md
|
||||
├── discuss-005-epics.md
|
||||
└── discuss-006-final.md
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 场景 | 处理 |
|
||||
|------|------|
|
||||
| Teammate 无响应 | 发追踪消息,2次无响应 → 重新 spawn |
|
||||
| 讨论无法共识 | Coordinator 介入 → AskUserQuestion |
|
||||
| 文档质量 <60% | 创建 DRAFT-fix 任务给 writer |
|
||||
| Writer 修订 3+ 次 | 上报用户,建议调整范围 |
|
||||
| Research 无法完成 | 降级为简化模式,跳过深度分析 |
|
||||
@@ -1,498 +0,0 @@
|
||||
---
|
||||
name: spec-discuss
|
||||
description: Team spec discuss - 结构化团队讨论、多视角批判、共识构建、分歧调解
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Spec Discuss Command (/team:spec-discuss)
|
||||
|
||||
## Overview
|
||||
|
||||
Team spec-discuss role command. Operates as a teammate within a Spec Team, responsible for facilitating structured team discussions between specification phases. This is the **key differentiator** of the spec team workflow — ensuring multi-perspective critique, consensus building, and quality feedback before each phase transition.
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (DISCUSS-* tasks)
|
||||
- Multi-perspective analysis: Product, Technical, Quality, Risk viewpoints
|
||||
- Structured discussion facilitation with critique + suggestion format
|
||||
- Consensus synthesis with action items and decision records
|
||||
- Conflict identification and escalation when consensus cannot be reached
|
||||
- CLI-assisted deep critique (parallel multi-model analysis)
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `spec-discuss`
|
||||
**Responsibility**: Load Artifact → Multi-Perspective Critique → Synthesize Consensus → Report
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `discussion_ready` | spec-discuss → coordinator | 讨论完成,共识达成 | 附带讨论记录路径和决策摘要 |
|
||||
| `discussion_blocked` | spec-discuss → coordinator | 讨论无法达成共识 | 附带分歧点和可选方案,需 coordinator 介入 |
|
||||
| `impl_progress` | spec-discuss → coordinator | 长讨论进展更新 | 多视角分析进度 |
|
||||
| `error` | spec-discuss → coordinator | 讨论无法进行 | 输入制品缺失等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 讨论共识达成
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "discussion_ready", summary: "DISCUSS-002: 共识达成, 3个改进建议 + 2个开放问题", ref: ".workflow/.spec-team/session/discussions/discuss-002-brief.md" })
|
||||
|
||||
// 讨论阻塞
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "discussion_blocked", summary: "DISCUSS-004: 技术选型分歧 - 微服务 vs 单体, 需用户决策", data: { reason: "技术架构风格无法达成共识", options: [{ label: "微服务", description: "更好扩展性但增加复杂度" }, { label: "单体", description: "简单但限制扩展" }] } })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: "DISCUSS-001: 找不到 discovery-context.json" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "spec-discuss" --to "coordinator" --type "discussion_ready" --summary "DISCUSS-002: 共识达成, 3个改进建议" --ref "${sessionFolder}/discussions/discuss-002-brief.md" --json`)
|
||||
|
||||
// 带 data 参数(讨论阻塞时)
|
||||
Bash(`ccw team log --team "${teamName}" --from "spec-discuss" --to "coordinator" --type "discussion_blocked" --summary "技术选型分歧" --data '{"reason":"微服务 vs 单体","options":[{"label":"微服务"},{"label":"单体"}]}' --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from spec-discuss --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]`
|
||||
|
||||
## 讨论维度模型
|
||||
|
||||
每个讨论轮次从4个视角进行结构化分析:
|
||||
|
||||
| 视角 | 关注点 | 代表角色 |
|
||||
|------|--------|----------|
|
||||
| **产品视角** | 市场适配、用户价值、商业可行性、竞品差异 | Product Manager |
|
||||
| **技术视角** | 可行性、技术债务、性能、安全、可维护性 | Tech Lead |
|
||||
| **质量视角** | 完整性、可测试性、一致性、标准合规 | QA Lead |
|
||||
| **风险视角** | 风险识别、依赖分析、假设验证、失败模式 | Risk Analyst |
|
||||
|
||||
## 讨论轮次配置
|
||||
|
||||
| 轮次 | 制品 | 重点维度 | 讨论深度 |
|
||||
|------|------|----------|----------|
|
||||
| DISCUSS-001 | discovery-context | 产品+风险 | 范围确认、方向调整 |
|
||||
| DISCUSS-002 | product-brief | 产品+技术+质量 | 定位审视、可行性 |
|
||||
| DISCUSS-003 | requirements | 质量+产品 | 完整性、优先级 |
|
||||
| DISCUSS-004 | architecture | 技术+风险 | 技术选型、安全性 |
|
||||
| DISCUSS-005 | epics | 产品+技术+质量 | MVP范围、估算 |
|
||||
| DISCUSS-006 | readiness-report | 全维度 | 最终签收 |
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked DISCUSS-* tasks
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Artifact Loading
|
||||
├─ Identify discussion round from task subject (001-006)
|
||||
├─ Load target artifact for discussion
|
||||
├─ Load prior discussion records for continuity
|
||||
└─ Determine discussion dimensions from round config
|
||||
|
||||
Phase 3: Multi-Perspective Critique
|
||||
├─ Product perspective analysis
|
||||
├─ Technical perspective analysis
|
||||
├─ Quality perspective analysis
|
||||
├─ Risk perspective analysis
|
||||
└─ (Parallel CLI execution for depth)
|
||||
|
||||
Phase 4: Consensus Synthesis
|
||||
├─ Identify convergent themes (areas of agreement)
|
||||
├─ Identify divergent views (conflicts)
|
||||
├─ Generate action items and recommendations
|
||||
├─ Formulate consensus or escalate divergence
|
||||
└─ Write discussion record
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ team_msg log + SendMessage discussion results
|
||||
├─ TaskUpdate completed (if consensus)
|
||||
└─ Flag discussion_blocked (if unresolvable conflict)
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find assigned DISCUSS-* tasks
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('DISCUSS-') &&
|
||||
t.owner === 'spec-discuss' &&
|
||||
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: Artifact Loading
|
||||
|
||||
```javascript
|
||||
// Extract session folder and discussion round
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
const roundMatch = task.subject.match(/DISCUSS-(\d+)/)
|
||||
const roundNumber = roundMatch ? parseInt(roundMatch[1]) : 0
|
||||
|
||||
// Discussion round configuration
|
||||
const roundConfig = {
|
||||
1: { artifact: 'discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk'], label: '范围讨论' },
|
||||
2: { artifact: 'product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality'], label: 'Brief评审' },
|
||||
3: { artifact: 'requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product'], label: '需求讨论' },
|
||||
4: { artifact: 'architecture/_index.md', type: 'md', outputFile: 'discuss-004-architecture.md', perspectives: ['technical', 'risk'], label: '架构讨论' },
|
||||
5: { artifact: 'epics/_index.md', type: 'md', outputFile: 'discuss-005-epics.md', perspectives: ['product', 'technical', 'quality'], label: 'Epics讨论' },
|
||||
6: { artifact: 'readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk'], label: '最终签收' }
|
||||
}
|
||||
|
||||
const config = roundConfig[roundNumber]
|
||||
if (!config) {
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: `未知讨论轮次: DISCUSS-${roundNumber}` })
|
||||
return
|
||||
}
|
||||
|
||||
// Load target artifact
|
||||
let artifact = null
|
||||
try {
|
||||
const raw = Read(`${sessionFolder}/${config.artifact}`)
|
||||
artifact = config.type === 'json' ? JSON.parse(raw) : raw
|
||||
} catch (e) {
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "error", summary: `无法加载制品: ${config.artifact}` })
|
||||
return
|
||||
}
|
||||
|
||||
// Load prior discussion records for context continuity
|
||||
const priorDiscussions = []
|
||||
for (let i = 1; i < roundNumber; i++) {
|
||||
const priorConfig = roundConfig[i]
|
||||
try { priorDiscussions.push(Read(`${sessionFolder}/discussions/${priorConfig.outputFile}`)) } catch {}
|
||||
}
|
||||
|
||||
// Ensure discussions directory exists
|
||||
Bash(`mkdir -p ${sessionFolder}/discussions`)
|
||||
```
|
||||
|
||||
### Phase 3: Multi-Perspective Critique
|
||||
|
||||
```javascript
|
||||
const perspectives = {}
|
||||
const artifactContent = typeof artifact === 'string' ? artifact : JSON.stringify(artifact, null, 2)
|
||||
|
||||
// Progress notification
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-discuss", to: "coordinator", type: "impl_progress", summary: `开始 ${config.label}: ${config.perspectives.length} 个视角分析` })
|
||||
|
||||
// --- Product Perspective ---
|
||||
if (config.perspectives.includes('product')) {
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a PRODUCT MANAGER perspective.
|
||||
TASK:
|
||||
• Evaluate market fit and user value proposition
|
||||
• Assess target user alignment and persona coverage
|
||||
• Check business viability and competitive differentiation
|
||||
• Identify gaps in user journey coverage
|
||||
• Rate on scale 1-5 with specific improvement suggestions
|
||||
|
||||
ARTIFACT TYPE: ${config.label}
|
||||
CONTENT: ${artifactContent.substring(0, 8000)}
|
||||
|
||||
${priorDiscussions.length > 0 ? `PRIOR DISCUSSION CONTEXT: ${priorDiscussions[priorDiscussions.length - 1]?.substring(0, 2000)}` : ''}
|
||||
|
||||
EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], open_questions[], rating (1-5)
|
||||
CONSTRAINTS: Be constructive but rigorous" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
// perspectives.product = parseCLIResult(...)
|
||||
}
|
||||
|
||||
// --- Technical Perspective ---
|
||||
if (config.perspectives.includes('technical')) {
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a TECH LEAD perspective.
|
||||
TASK:
|
||||
• Evaluate technical feasibility and implementation complexity
|
||||
• Assess architecture decisions and technology choices
|
||||
• Check for technical debt risks and scalability concerns
|
||||
• Identify missing technical requirements or constraints
|
||||
• Rate on scale 1-5 with specific improvement suggestions
|
||||
|
||||
ARTIFACT TYPE: ${config.label}
|
||||
CONTENT: ${artifactContent.substring(0, 8000)}
|
||||
|
||||
EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], risks[], rating (1-5)
|
||||
CONSTRAINTS: Focus on engineering concerns" --tool codex --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
// perspectives.technical = parseCLIResult(...)
|
||||
}
|
||||
|
||||
// --- Quality Perspective ---
|
||||
if (config.perspectives.includes('quality')) {
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a QA LEAD perspective.
|
||||
TASK:
|
||||
• Evaluate completeness of acceptance criteria and testability
|
||||
• Check consistency of terminology and formatting
|
||||
• Assess traceability between documents
|
||||
• Identify ambiguous or untestable requirements
|
||||
• Rate on scale 1-5 with specific improvement suggestions
|
||||
|
||||
ARTIFACT TYPE: ${config.label}
|
||||
CONTENT: ${artifactContent.substring(0, 8000)}
|
||||
|
||||
EXPECTED: Structured critique with: strengths[], weaknesses[], suggestions[], testability_issues[], rating (1-5)
|
||||
CONSTRAINTS: Focus on quality and verifiability" --tool claude --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
// perspectives.quality = parseCLIResult(...)
|
||||
}
|
||||
|
||||
// --- Risk Perspective ---
|
||||
if (config.perspectives.includes('risk')) {
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Critique the following specification artifact from a RISK ANALYST perspective.
|
||||
TASK:
|
||||
• Identify project risks and failure modes
|
||||
• Assess dependency risks and external factors
|
||||
• Validate assumptions made in the specification
|
||||
• Check for missing contingency plans
|
||||
• Rate risk level (Low/Medium/High/Critical) with mitigation suggestions
|
||||
|
||||
ARTIFACT TYPE: ${config.label}
|
||||
CONTENT: ${artifactContent.substring(0, 8000)}
|
||||
|
||||
EXPECTED: Structured critique with: risks[{description, likelihood, impact, mitigation}], assumptions_to_validate[], dependencies[], overall_risk_level
|
||||
CONSTRAINTS: Focus on risk identification" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
// perspectives.risk = parseCLIResult(...)
|
||||
}
|
||||
|
||||
// Wait for all parallel CLI analyses to complete
|
||||
```
|
||||
|
||||
### Phase 4: Consensus Synthesis
|
||||
|
||||
```javascript
|
||||
// Analyze all perspectives for convergence and divergence
|
||||
const synthesis = {
|
||||
convergent_themes: [], // Areas where all perspectives agree
|
||||
divergent_views: [], // Areas of conflict
|
||||
action_items: [], // Concrete improvements to make
|
||||
open_questions: [], // Unresolved questions for user/team
|
||||
decisions: [], // Decisions made during discussion
|
||||
risk_flags: [], // Risks identified
|
||||
overall_sentiment: '', // positive/neutral/concerns/critical
|
||||
consensus_reached: true // false if major unresolvable conflicts
|
||||
}
|
||||
|
||||
// Extract convergent themes (items mentioned positively by 2+ perspectives)
|
||||
// Extract divergent views (items where perspectives conflict)
|
||||
// Generate action items from suggestions
|
||||
|
||||
// Check for unresolvable conflicts
|
||||
const criticalDivergences = synthesis.divergent_views.filter(d => d.severity === 'high')
|
||||
if (criticalDivergences.length > 0) {
|
||||
synthesis.consensus_reached = false
|
||||
}
|
||||
|
||||
// Determine overall sentiment
|
||||
const avgRating = Object.values(perspectives)
|
||||
.map(p => p?.rating || 3)
|
||||
.reduce((a, b) => a + b, 0) / config.perspectives.length
|
||||
|
||||
synthesis.overall_sentiment = avgRating >= 4 ? 'positive'
|
||||
: avgRating >= 3 ? 'neutral'
|
||||
: avgRating >= 2 ? 'concerns'
|
||||
: 'critical'
|
||||
|
||||
// Generate discussion record
|
||||
const discussionRecord = `# 讨论记录: ${config.label} (DISCUSS-${String(roundNumber).padStart(3, '0')})
|
||||
|
||||
**讨论对象**: ${config.artifact}
|
||||
**参与视角**: ${config.perspectives.join(', ')}
|
||||
**讨论时间**: ${new Date().toISOString()}
|
||||
**共识状态**: ${synthesis.consensus_reached ? '已达成共识' : '存在分歧,需coordinator介入'}
|
||||
**总体评价**: ${synthesis.overall_sentiment}
|
||||
|
||||
## 多视角评审结果
|
||||
|
||||
${config.perspectives.map(p => {
|
||||
const pData = perspectives[p]
|
||||
return `### ${p === 'product' ? '产品视角 (Product Manager)' :
|
||||
p === 'technical' ? '技术视角 (Tech Lead)' :
|
||||
p === 'quality' ? '质量视角 (QA Lead)' :
|
||||
'风险视角 (Risk Analyst)'}
|
||||
|
||||
**评分**: ${pData?.rating || 'N/A'}/5
|
||||
|
||||
**优点**:
|
||||
${(pData?.strengths || []).map(s => '- ' + s).join('\n') || '- (待分析)'}
|
||||
|
||||
**不足**:
|
||||
${(pData?.weaknesses || []).map(w => '- ' + w).join('\n') || '- (待分析)'}
|
||||
|
||||
**建议**:
|
||||
${(pData?.suggestions || []).map(s => '- ' + s).join('\n') || '- (待分析)'}
|
||||
`}).join('\n')}
|
||||
|
||||
## 共识分析
|
||||
|
||||
### 一致认同的优点
|
||||
${synthesis.convergent_themes.map(t => '- ' + t).join('\n') || '- (待合成)'}
|
||||
|
||||
### 存在的分歧
|
||||
${synthesis.divergent_views.map(d => `- **${d.topic}**: ${d.description}`).join('\n') || '- 无重大分歧'}
|
||||
|
||||
### 风险标记
|
||||
${synthesis.risk_flags.map(r => `- [${r.level}] ${r.description}`).join('\n') || '- 无重大风险'}
|
||||
|
||||
## 行动项
|
||||
|
||||
${synthesis.action_items.map((item, i) => `${i+1}. ${item}`).join('\n') || '无需修改'}
|
||||
|
||||
## 开放问题
|
||||
|
||||
${synthesis.open_questions.map((q, i) => `${i+1}. ${q}`).join('\n') || '无开放问题'}
|
||||
|
||||
## 决策记录
|
||||
|
||||
${synthesis.decisions.map((d, i) => `${i+1}. **${d.topic}**: ${d.decision} (理由: ${d.rationale})`).join('\n') || '无新决策'}
|
||||
|
||||
## 对下一阶段的建议
|
||||
|
||||
${roundNumber < 6 ? `下一阶段应关注: ${synthesis.action_items.slice(0, 3).join('; ') || '按原计划推进'}` : '所有阶段已完成,建议进入执行。'}
|
||||
`
|
||||
|
||||
Write(`${sessionFolder}/discussions/${config.outputFile}`, discussionRecord)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
if (synthesis.consensus_reached) {
|
||||
// Consensus reached
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "spec-discuss", to: "coordinator",
|
||||
type: "discussion_ready",
|
||||
summary: `${config.label}讨论完成: ${synthesis.action_items.length}个行动项, ${synthesis.open_questions.length}个开放问题, 总体${synthesis.overall_sentiment}`,
|
||||
ref: `${sessionFolder}/discussions/${config.outputFile}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 讨论结果: ${config.label}
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**共识**: 已达成
|
||||
**总体评价**: ${synthesis.overall_sentiment}
|
||||
**参与视角**: ${config.perspectives.join(', ')}
|
||||
|
||||
### 关键发现
|
||||
**一致优点**: ${synthesis.convergent_themes.length}项
|
||||
**分歧点**: ${synthesis.divergent_views.length}项
|
||||
**风险标记**: ${synthesis.risk_flags.length}项
|
||||
|
||||
### 行动项 (${synthesis.action_items.length})
|
||||
${synthesis.action_items.map((item, i) => `${i+1}. ${item}`).join('\n') || '无'}
|
||||
|
||||
### 开放问题 (${synthesis.open_questions.length})
|
||||
${synthesis.open_questions.map((q, i) => `${i+1}. ${q}`).join('\n') || '无'}
|
||||
|
||||
### 讨论记录
|
||||
${sessionFolder}/discussions/${config.outputFile}
|
||||
|
||||
共识已达成,可推进至下一阶段。`,
|
||||
summary: `${config.label}共识达成: ${synthesis.action_items.length}行动项`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Consensus blocked - escalate to coordinator
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "spec-discuss", to: "coordinator",
|
||||
type: "discussion_blocked",
|
||||
summary: `${config.label}讨论阻塞: ${criticalDivergences.length}个关键分歧需决策`,
|
||||
data: {
|
||||
reason: criticalDivergences.map(d => d.description).join('; '),
|
||||
options: criticalDivergences.map(d => ({
|
||||
label: d.topic,
|
||||
description: d.options?.join(' vs ') || d.description
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 讨论阻塞: ${config.label}
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**状态**: 无法达成共识,需要 coordinator 介入
|
||||
|
||||
### 关键分歧
|
||||
${criticalDivergences.map((d, i) => `${i+1}. **${d.topic}**: ${d.description}
|
||||
- 选项A: ${d.optionA || ''}
|
||||
- 选项B: ${d.optionB || ''}`).join('\n\n')}
|
||||
|
||||
### 已达成共识的部分
|
||||
${synthesis.convergent_themes.map(t => `- ${t}`).join('\n') || '- 无'}
|
||||
|
||||
### 建议
|
||||
请通过 AskUserQuestion 收集用户对分歧点的决策,然后将决策写入讨论记录以继续推进。
|
||||
|
||||
### 讨论记录(部分)
|
||||
${sessionFolder}/discussions/${config.outputFile}`,
|
||||
summary: `${config.label}阻塞: ${criticalDivergences.length}分歧`
|
||||
})
|
||||
|
||||
// Keep task in_progress, wait for coordinator resolution
|
||||
}
|
||||
|
||||
// Check for next DISCUSS task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('DISCUSS-') &&
|
||||
t.owner === 'spec-discuss' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task -> back to Phase 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No DISCUSS-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Target artifact not found | Notify coordinator, request prerequisite completion |
|
||||
| CLI perspective analysis failure | Fallback to direct Claude analysis for that perspective |
|
||||
| All CLI analyses fail | Generate basic discussion from direct reading |
|
||||
| Consensus timeout (all perspectives diverge) | Escalate as discussion_blocked |
|
||||
| Prior discussion records missing | Continue without continuity context |
|
||||
| Session folder not found | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
@@ -1,492 +0,0 @@
|
||||
---
|
||||
name: spec-reviewer
|
||||
description: Team spec reviewer - 跨文档质量验证、完整性/一致性/可追溯性/深度评分、就绪度检查
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Spec Reviewer Command (/team:spec-reviewer)
|
||||
|
||||
## Overview
|
||||
|
||||
Team spec-reviewer role command. Operates as a teammate within a Spec Team, responsible for cross-document quality validation and readiness checks. Maps to spec-generator Phase 6 (Readiness Check).
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (QUALITY-* tasks)
|
||||
- 4-dimension quality scoring: Completeness, Consistency, Traceability, Depth
|
||||
- Cross-document validation (Brief → PRD → Architecture → Epics chain)
|
||||
- Quality gate enforcement (Pass ≥80%, Review 60-79%, Fail <60%)
|
||||
- Readiness report and executive summary generation
|
||||
- CLI-assisted deep validation (optional)
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `spec-reviewer`
|
||||
**Responsibility**: Load All Documents → Cross-Validate → Score → Report
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-reviewer", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `quality_result` | spec-reviewer → coordinator | 质量检查完成 | 附带评分和 gate 决策 (PASS/REVIEW/FAIL) |
|
||||
| `fix_required` | spec-reviewer → coordinator | 发现关键质量问题 | 需创建 DRAFT-fix 任务 |
|
||||
| `error` | spec-reviewer → coordinator | 审查无法完成 | 文档缺失、无法解析等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 质量通过
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-reviewer", to: "coordinator", type: "quality_result", summary: "质量检查 PASS: 85分 (完整性90/一致性85/可追溯性80/深度85)", data: { gate: "PASS", score: 85 } })
|
||||
|
||||
// 需要审查
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-reviewer", to: "coordinator", type: "quality_result", summary: "质量检查 REVIEW: 72分, 可追溯性不足", data: { gate: "REVIEW", score: 72 } })
|
||||
|
||||
// 质量失败
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-reviewer", to: "coordinator", type: "fix_required", summary: "质量 FAIL: 55分, 缺少架构 ADR + PRD 验收标准不可测", data: { gate: "FAIL", score: 55, issues: ["missing ADRs", "untestable AC"] } })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "spec-reviewer" --to "coordinator" --type "quality_result" --summary "质量检查 PASS: 85分" --data '{"gate":"PASS","score":85}' --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from spec-reviewer --to coordinator --type <type> --summary "<text>" [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked QUALITY-* tasks
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Document Collection
|
||||
├─ Load all generated documents from session folder
|
||||
├─ Verify document chain completeness
|
||||
├─ Load discussion records for context
|
||||
└─ Build document inventory
|
||||
|
||||
Phase 3: 4-Dimension Quality Scoring
|
||||
├─ Completeness (25%): All sections present with content
|
||||
├─ Consistency (25%): Terminology, format, references
|
||||
├─ Traceability (25%): Goals → Reqs → Arch → Stories chain
|
||||
└─ Depth (25%): AC testable, ADRs justified, stories estimable
|
||||
|
||||
Phase 4: Report Generation
|
||||
├─ Generate readiness-report.md (quality scores, issues, traceability)
|
||||
├─ Generate spec-summary.md (one-page executive summary)
|
||||
└─ Determine quality gate decision
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ team_msg log + SendMessage quality results
|
||||
├─ TaskUpdate completed (if PASS/REVIEW)
|
||||
└─ Flag fix_required (if FAIL)
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find assigned QUALITY-* tasks
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('QUALITY-') &&
|
||||
t.owner === 'spec-reviewer' &&
|
||||
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: Document Collection
|
||||
|
||||
```javascript
|
||||
// Extract session folder
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load all documents
|
||||
const documents = {
|
||||
config: null,
|
||||
discoveryContext: null,
|
||||
productBrief: null,
|
||||
requirementsIndex: null,
|
||||
requirements: [],
|
||||
architectureIndex: null,
|
||||
adrs: [],
|
||||
epicsIndex: null,
|
||||
epics: [],
|
||||
discussions: []
|
||||
}
|
||||
|
||||
try { documents.config = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/epics/_index.md`) } catch {}
|
||||
|
||||
// Load individual requirements
|
||||
const reqFiles = Glob({ pattern: `${sessionFolder}/requirements/REQ-*.md` })
|
||||
reqFiles.forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
const nfrFiles = Glob({ pattern: `${sessionFolder}/requirements/NFR-*.md` })
|
||||
nfrFiles.forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
|
||||
// Load individual ADRs
|
||||
const adrFiles = Glob({ pattern: `${sessionFolder}/architecture/ADR-*.md` })
|
||||
adrFiles.forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
|
||||
// Load individual Epics
|
||||
const epicFiles = Glob({ pattern: `${sessionFolder}/epics/EPIC-*.md` })
|
||||
epicFiles.forEach(f => { try { documents.epics.push(Read(f)) } catch {} })
|
||||
|
||||
// Load discussions
|
||||
const discussFiles = Glob({ pattern: `${sessionFolder}/discussions/discuss-*.md` })
|
||||
discussFiles.forEach(f => { try { documents.discussions.push(Read(f)) } catch {} })
|
||||
|
||||
// Verify completeness
|
||||
const docInventory = {
|
||||
config: !!documents.config,
|
||||
discoveryContext: !!documents.discoveryContext,
|
||||
productBrief: !!documents.productBrief,
|
||||
requirements: documents.requirements.length > 0,
|
||||
architecture: documents.adrs.length > 0,
|
||||
epics: documents.epics.length > 0,
|
||||
discussions: documents.discussions.length
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: 4-Dimension Quality Scoring
|
||||
|
||||
```javascript
|
||||
const scores = {
|
||||
completeness: 0,
|
||||
consistency: 0,
|
||||
traceability: 0,
|
||||
depth: 0
|
||||
}
|
||||
|
||||
// ===== Completeness (25%) =====
|
||||
function scoreCompleteness(docs) {
|
||||
let score = 0
|
||||
const checks = [
|
||||
{ name: 'spec-config.json', present: !!docs.config, weight: 5 },
|
||||
{ name: 'discovery-context.json', present: !!docs.discoveryContext, weight: 10 },
|
||||
{ name: 'product-brief.md', present: !!docs.productBrief, weight: 20 },
|
||||
{ name: 'requirements/_index.md', present: !!docs.requirementsIndex, weight: 15 },
|
||||
{ name: 'REQ-* files', present: docs.requirements.length > 0, weight: 10 },
|
||||
{ name: 'architecture/_index.md', present: !!docs.architectureIndex, weight: 15 },
|
||||
{ name: 'ADR-* files', present: docs.adrs.length > 0, weight: 10 },
|
||||
{ name: 'epics/_index.md', present: !!docs.epicsIndex, weight: 10 },
|
||||
{ name: 'EPIC-* files', present: docs.epics.length > 0, weight: 5 }
|
||||
]
|
||||
|
||||
checks.forEach(check => {
|
||||
if (check.present) score += check.weight
|
||||
})
|
||||
|
||||
return { score, checks, issues: checks.filter(c => !c.present).map(c => `Missing: ${c.name}`) }
|
||||
}
|
||||
|
||||
// ===== Consistency (25%) =====
|
||||
function scoreConsistency(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
|
||||
// Check session_id consistency across documents
|
||||
const sessionId = docs.config?.session_id
|
||||
if (sessionId) {
|
||||
if (docs.productBrief && !docs.productBrief.includes(sessionId)) {
|
||||
score -= 15; issues.push('Product Brief missing session_id reference')
|
||||
}
|
||||
}
|
||||
|
||||
// Check terminology consistency
|
||||
// Extract key terms from product brief, verify usage in other docs
|
||||
if (docs.productBrief && docs.requirementsIndex) {
|
||||
// Basic term consistency check
|
||||
const briefTerms = docs.productBrief.match(/##\s+(.+)/g)?.map(h => h.replace('## ', '')) || []
|
||||
// Verify heading style consistency
|
||||
}
|
||||
|
||||
// Check YAML frontmatter format consistency
|
||||
const docsWithFrontmatter = [docs.productBrief, docs.requirementsIndex, docs.architectureIndex, docs.epicsIndex].filter(Boolean)
|
||||
const hasFrontmatter = docsWithFrontmatter.map(d => /^---\n[\s\S]+?\n---/.test(d))
|
||||
const frontmatterConsistent = hasFrontmatter.every(v => v === hasFrontmatter[0])
|
||||
if (!frontmatterConsistent) {
|
||||
score -= 20; issues.push('Inconsistent YAML frontmatter across documents')
|
||||
}
|
||||
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
// ===== Traceability (25%) =====
|
||||
function scoreTraceability(docs) {
|
||||
let score = 0
|
||||
const issues = []
|
||||
|
||||
// Goals → Requirements tracing
|
||||
if (docs.productBrief && docs.requirementsIndex) {
|
||||
// Check if requirements reference product brief goals
|
||||
const hasGoalRefs = docs.requirements.some(r => /goal|brief|vision/i.test(r))
|
||||
if (hasGoalRefs) score += 25
|
||||
else issues.push('Requirements lack references to Product Brief goals')
|
||||
}
|
||||
|
||||
// Requirements → Architecture tracing
|
||||
if (docs.requirementsIndex && docs.architectureIndex) {
|
||||
const hasReqRefs = docs.adrs.some(a => /REQ-|requirement/i.test(a))
|
||||
if (hasReqRefs) score += 25
|
||||
else issues.push('Architecture ADRs lack requirement references')
|
||||
}
|
||||
|
||||
// Requirements → Stories tracing
|
||||
if (docs.requirementsIndex && docs.epicsIndex) {
|
||||
const hasStoryRefs = docs.epics.some(e => /REQ-|requirement/i.test(e))
|
||||
if (hasStoryRefs) score += 25
|
||||
else issues.push('Epics/Stories lack requirement tracing')
|
||||
}
|
||||
|
||||
// Full chain check
|
||||
if (score >= 50) score += 25 // bonus for good overall traceability
|
||||
|
||||
return { score: Math.min(100, score), issues }
|
||||
}
|
||||
|
||||
// ===== Depth (25%) =====
|
||||
function scoreDepth(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
|
||||
// Check acceptance criteria specificity
|
||||
const acPattern = /acceptance|criteria|验收/i
|
||||
const hasSpecificAC = docs.requirements.some(r => acPattern.test(r) && r.length > 200)
|
||||
if (!hasSpecificAC) {
|
||||
score -= 25; issues.push('Acceptance criteria may lack specificity')
|
||||
}
|
||||
|
||||
// Check ADR justification depth
|
||||
const adrHasAlternatives = docs.adrs.some(a => /alternative|替代|pros|cons/i.test(a))
|
||||
if (!adrHasAlternatives && docs.adrs.length > 0) {
|
||||
score -= 25; issues.push('ADRs lack alternatives analysis')
|
||||
}
|
||||
|
||||
// Check story estimability
|
||||
const storySized = docs.epics.some(e => /\b[SMLX]{1,2}\b|Small|Medium|Large/.test(e))
|
||||
if (!storySized && docs.epics.length > 0) {
|
||||
score -= 25; issues.push('Stories lack size estimates')
|
||||
}
|
||||
|
||||
// Check Mermaid diagrams presence
|
||||
const hasDiagrams = [docs.architectureIndex, docs.epicsIndex].some(d => d && /```mermaid/.test(d))
|
||||
if (!hasDiagrams) {
|
||||
score -= 10; issues.push('Missing Mermaid diagrams')
|
||||
}
|
||||
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
// Execute all scoring
|
||||
const completenessResult = scoreCompleteness(documents)
|
||||
const consistencyResult = scoreConsistency(documents)
|
||||
const traceabilityResult = scoreTraceability(documents)
|
||||
const depthResult = scoreDepth(documents)
|
||||
|
||||
scores.completeness = completenessResult.score
|
||||
scores.consistency = consistencyResult.score
|
||||
scores.traceability = traceabilityResult.score
|
||||
scores.depth = depthResult.score
|
||||
|
||||
const overallScore = (scores.completeness + scores.consistency + scores.traceability + scores.depth) / 4
|
||||
const qualityGate = overallScore >= 80 ? 'PASS' : overallScore >= 60 ? 'REVIEW' : 'FAIL'
|
||||
```
|
||||
|
||||
### Phase 4: Report Generation
|
||||
|
||||
```javascript
|
||||
// Generate readiness-report.md
|
||||
const readinessReport = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: readiness-report
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Readiness Report
|
||||
|
||||
## Quality Scores
|
||||
|
||||
| Dimension | Score | Weight |
|
||||
|-----------|-------|--------|
|
||||
| Completeness | ${scores.completeness}% | 25% |
|
||||
| Consistency | ${scores.consistency}% | 25% |
|
||||
| Traceability | ${scores.traceability}% | 25% |
|
||||
| Depth | ${scores.depth}% | 25% |
|
||||
| **Overall** | **${overallScore.toFixed(1)}%** | **100%** |
|
||||
|
||||
## Quality Gate: ${qualityGate}
|
||||
|
||||
${qualityGate === 'PASS' ? 'All quality criteria met. Specification is ready for execution.' :
|
||||
qualityGate === 'REVIEW' ? 'Quality is acceptable with some areas needing attention.' :
|
||||
'Critical quality issues must be addressed before proceeding.'}
|
||||
|
||||
## Issues Found
|
||||
|
||||
### Completeness Issues
|
||||
${completenessResult.issues.map(i => `- ${i}`).join('\n') || 'None'}
|
||||
|
||||
### Consistency Issues
|
||||
${consistencyResult.issues.map(i => `- ${i}`).join('\n') || 'None'}
|
||||
|
||||
### Traceability Issues
|
||||
${traceabilityResult.issues.map(i => `- ${i}`).join('\n') || 'None'}
|
||||
|
||||
### Depth Issues
|
||||
${depthResult.issues.map(i => `- ${i}`).join('\n') || 'None'}
|
||||
|
||||
## Document Inventory
|
||||
${Object.entries(docInventory).map(([k, v]) => `- ${k}: ${v === true ? '✓' : v === false ? '✗' : v}`).join('\n')}
|
||||
|
||||
## Discussion Rounds Completed: ${documents.discussions.length}
|
||||
|
||||
## Recommendations
|
||||
${allIssues.map(i => `- ${i}`).join('\n') || 'No outstanding issues.'}
|
||||
`
|
||||
Write(`${sessionFolder}/readiness-report.md`, readinessReport)
|
||||
|
||||
// Generate spec-summary.md (one-page executive summary)
|
||||
const specSummary = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: spec-summary
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Specification Summary
|
||||
|
||||
**Topic**: ${documents.config?.topic || 'N/A'}
|
||||
**Complexity**: ${documents.config?.complexity || 'N/A'}
|
||||
**Quality Score**: ${overallScore.toFixed(1)}% (${qualityGate})
|
||||
**Discussion Rounds**: ${documents.discussions.length}
|
||||
|
||||
## Key Deliverables
|
||||
- Product Brief: ${docInventory.productBrief ? '✓' : '✗'}
|
||||
- Requirements (PRD): ${docInventory.requirements ? `✓ (${documents.requirements.length} items)` : '✗'}
|
||||
- Architecture: ${docInventory.architecture ? `✓ (${documents.adrs.length} ADRs)` : '✗'}
|
||||
- Epics & Stories: ${docInventory.epics ? `✓ (${documents.epics.length} epics)` : '✗'}
|
||||
|
||||
## Next Steps
|
||||
${qualityGate === 'PASS' ? '- Ready for handoff to execution workflows (lite-plan, req-plan, plan, issue:new)' :
|
||||
qualityGate === 'REVIEW' ? '- Address review items, then proceed to execution' :
|
||||
'- Fix critical issues before proceeding'}
|
||||
`
|
||||
Write(`${sessionFolder}/spec-summary.md`, specSummary)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const allIssues = [
|
||||
...completenessResult.issues,
|
||||
...consistencyResult.issues,
|
||||
...traceabilityResult.issues,
|
||||
...depthResult.issues
|
||||
]
|
||||
|
||||
// Log before SendMessage
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "spec-reviewer", to: "coordinator",
|
||||
type: qualityGate === 'FAIL' ? "fix_required" : "quality_result",
|
||||
summary: `质量检查 ${qualityGate}: ${overallScore.toFixed(1)}分 (完整性${scores.completeness}/一致性${scores.consistency}/追溯${scores.traceability}/深度${scores.depth})`,
|
||||
data: { gate: qualityGate, score: overallScore, issues: allIssues }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 质量审查报告
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**总分**: ${overallScore.toFixed(1)}%
|
||||
**Gate**: ${qualityGate}
|
||||
|
||||
### 评分详情
|
||||
| 维度 | 分数 |
|
||||
|------|------|
|
||||
| 完整性 | ${scores.completeness}% |
|
||||
| 一致性 | ${scores.consistency}% |
|
||||
| 可追溯性 | ${scores.traceability}% |
|
||||
| 深度 | ${scores.depth}% |
|
||||
|
||||
### 问题列表 (${allIssues.length})
|
||||
${allIssues.map(i => `- ${i}`).join('\n') || '无问题'}
|
||||
|
||||
### 文档清单
|
||||
${Object.entries(docInventory).map(([k, v]) => `- ${k}: ${typeof v === 'boolean' ? (v ? '✓' : '✗') : v}`).join('\n')}
|
||||
|
||||
### 讨论轮次: ${documents.discussions.length}
|
||||
|
||||
### 输出位置
|
||||
- 就绪报告: ${sessionFolder}/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec-summary.md
|
||||
|
||||
${qualityGate === 'PASS' ? '质量达标,可进入最终讨论轮次 DISCUSS-006。' :
|
||||
qualityGate === 'REVIEW' ? '质量基本达标但有改进空间,建议在讨论中审查。' :
|
||||
'质量未达标,建议创建 DRAFT-fix 任务修复关键问题。'}`,
|
||||
summary: `质量 ${qualityGate}: ${overallScore.toFixed(1)}分`
|
||||
})
|
||||
|
||||
// Mark task
|
||||
if (qualityGate !== 'FAIL') {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Keep in_progress, coordinator needs to create fix tasks
|
||||
}
|
||||
|
||||
// Check for next QUALITY task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('QUALITY-') &&
|
||||
t.owner === 'spec-reviewer' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task -> back to Phase 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No QUALITY-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Documents missing from session | Score as 0 for completeness, report to coordinator |
|
||||
| Cannot parse YAML frontmatter | Skip consistency check for that document |
|
||||
| Session folder not found | Notify coordinator, request session path |
|
||||
| Scoring produces NaN | Default to 0 for that dimension, log warning |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
@@ -1,492 +0,0 @@
|
||||
---
|
||||
name: spec-writer
|
||||
description: Team spec writer - 产品简报/需求文档/架构文档/史诗故事撰写、模板驱动文档生成
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
|
||||
# Team Spec Writer Command (/team:spec-writer)
|
||||
|
||||
## Overview
|
||||
|
||||
Team spec-writer role command. Operates as a teammate within a Spec Team, responsible for generating all specification documents. Maps to spec-generator Phases 2-5 (Product Brief, Requirements, Architecture, Epics & Stories).
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (DRAFT-* tasks)
|
||||
- Complexity-adaptive writing (Low → direct, Medium/High → multi-CLI analysis)
|
||||
- Multi-perspective document generation (产品/技术/用户 parallel analysis)
|
||||
- Template-driven output following spec-generator document standards
|
||||
- Discussion feedback incorporation for iterative refinement
|
||||
|
||||
## Role Definition
|
||||
|
||||
**Name**: `spec-writer`
|
||||
**Responsibility**: Load Context → Generate Document → Incorporate Feedback → Report
|
||||
**Communication**: SendMessage to coordinator only
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-writer", to: "coordinator", type: "<type>", summary: "<摘要>", ref: "<文件路径>" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `draft_ready` | spec-writer → coordinator | 文档撰写完成 | 附带文档路径和类型 |
|
||||
| `draft_revision` | spec-writer → coordinator | 文档修订后重新提交 | 说明修改内容 |
|
||||
| `impl_progress` | spec-writer → coordinator | 长时间撰写进展 | 多文档阶段进度 |
|
||||
| `error` | spec-writer → coordinator | 遇到不可恢复错误 | 模板缺失、上下文不足等 |
|
||||
|
||||
### 调用示例
|
||||
|
||||
```javascript
|
||||
// 文档就绪
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-writer", to: "coordinator", type: "draft_ready", summary: "Product Brief 完成: 8个章节, 3视角合成", ref: ".workflow/.spec-team/session/product-brief.md" })
|
||||
|
||||
// 文档修订
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-writer", to: "coordinator", type: "draft_revision", summary: "PRD 已按讨论反馈修订: 新增2个NFR, 调整3个优先级" })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-writer", to: "coordinator", type: "error", summary: "缺少 discovery-context.json, 无法生成 Product Brief" })
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "spec-writer" --to "coordinator" --type "draft_ready" --summary "Product Brief 完成: 8个章节" --ref "${sessionFolder}/product-brief.md" --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from spec-writer --to coordinator --type <type> --summary "<text>" [--ref <path>] [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked DRAFT-* tasks
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Context & Discussion Loading
|
||||
├─ Read session config (spec-config.json)
|
||||
├─ Read relevant prior documents and discussion records
|
||||
├─ Determine document type from task subject (Brief/PRD/Architecture/Epics)
|
||||
└─ Load discussion feedback (discuss-*.md)
|
||||
|
||||
Phase 3: Document Generation (type-specific)
|
||||
├─ DRAFT-001: Product Brief (multi-CLI parallel analysis)
|
||||
├─ DRAFT-002: Requirements/PRD (functional + non-functional + MoSCoW)
|
||||
├─ DRAFT-003: Architecture (ADRs + tech stack + diagrams)
|
||||
└─ DRAFT-004: Epics & Stories (decomposition + dependencies + MVP)
|
||||
|
||||
Phase 4: Self-Validation
|
||||
├─ Check all template sections populated
|
||||
├─ Verify cross-references to prior documents
|
||||
└─ Validate YAML frontmatter completeness
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ team_msg log + SendMessage document summary
|
||||
├─ TaskUpdate completed
|
||||
└─ Check for next DRAFT-* task
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find assigned DRAFT-* tasks
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('DRAFT-') &&
|
||||
t.owner === 'spec-writer' &&
|
||||
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: Context & Discussion Loading
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load session config
|
||||
let specConfig = null
|
||||
try { specConfig = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
|
||||
// Determine document type from task subject
|
||||
const docType = task.subject.includes('Product Brief') ? 'product-brief'
|
||||
: task.subject.includes('Requirements') || task.subject.includes('PRD') ? 'requirements'
|
||||
: task.subject.includes('Architecture') ? 'architecture'
|
||||
: task.subject.includes('Epics') ? 'epics'
|
||||
: 'unknown'
|
||||
|
||||
// Load discussion feedback (from preceding DISCUSS task)
|
||||
const discussionFiles = {
|
||||
'product-brief': 'discussions/discuss-001-scope.md',
|
||||
'requirements': 'discussions/discuss-002-brief.md',
|
||||
'architecture': 'discussions/discuss-003-requirements.md',
|
||||
'epics': 'discussions/discuss-004-architecture.md'
|
||||
}
|
||||
let discussionFeedback = null
|
||||
try {
|
||||
discussionFeedback = Read(`${sessionFolder}/${discussionFiles[docType]}`)
|
||||
} catch {}
|
||||
|
||||
// Load prior documents
|
||||
const priorDocs = {}
|
||||
if (docType !== 'product-brief') {
|
||||
try { priorDocs.discoveryContext = Read(`${sessionFolder}/discovery-context.json`) } catch {}
|
||||
}
|
||||
if (docType === 'requirements' || docType === 'architecture' || docType === 'epics') {
|
||||
try { priorDocs.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
}
|
||||
if (docType === 'architecture' || docType === 'epics') {
|
||||
try { priorDocs.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
}
|
||||
if (docType === 'epics') {
|
||||
try { priorDocs.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Document Generation
|
||||
|
||||
```javascript
|
||||
// Route to specific generation logic based on document type
|
||||
switch (docType) {
|
||||
case 'product-brief':
|
||||
await generateProductBrief(sessionFolder, specConfig, discussionFeedback)
|
||||
break
|
||||
case 'requirements':
|
||||
await generateRequirements(sessionFolder, specConfig, priorDocs, discussionFeedback)
|
||||
break
|
||||
case 'architecture':
|
||||
await generateArchitecture(sessionFolder, specConfig, priorDocs, discussionFeedback)
|
||||
break
|
||||
case 'epics':
|
||||
await generateEpics(sessionFolder, specConfig, priorDocs, discussionFeedback)
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-001: Product Brief (Multi-Perspective Analysis)
|
||||
|
||||
```javascript
|
||||
async function generateProductBrief(sessionFolder, config, discussionFeedback) {
|
||||
const discoveryContext = JSON.parse(Read(`${sessionFolder}/discovery-context.json`))
|
||||
const topic = config?.topic || discoveryContext.seed_analysis.problem_statement
|
||||
|
||||
// 进展通知
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "spec-writer", to: "coordinator", type: "impl_progress", summary: "开始 Product Brief 多视角分析 (1/3)" })
|
||||
|
||||
// Launch 3 parallel CLI analyses for multi-perspective synthesis
|
||||
// 1. Product perspective (Gemini)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Analyze from PRODUCT perspective for specification.
|
||||
TASK: • Market fit analysis • Value proposition • Success criteria • Competitive landscape
|
||||
TOPIC: ${topic}
|
||||
CONTEXT: Discovery findings: ${JSON.stringify(discoveryContext.seed_analysis)}
|
||||
${discussionFeedback ? `DISCUSSION FEEDBACK: ${discussionFeedback}` : ''}
|
||||
EXPECTED: Structured product analysis (vision, problem, goals, scope, constraints)
|
||||
CONSTRAINTS: Focus on product strategy" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// 2. Technical perspective (Codex)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Analyze from TECHNICAL perspective for specification.
|
||||
TASK: • Technical feasibility • Architecture constraints • Tech stack recommendations • Implementation risks
|
||||
TOPIC: ${topic}
|
||||
CONTEXT: Discovery findings: ${JSON.stringify(discoveryContext.seed_analysis)}
|
||||
${discoveryContext.codebase_context ? `CODEBASE: ${JSON.stringify(discoveryContext.codebase_context)}` : ''}
|
||||
EXPECTED: Technical feasibility assessment
|
||||
CONSTRAINTS: Focus on engineering perspective" --tool codex --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// 3. User perspective (Claude)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Analyze from USER perspective for specification.
|
||||
TASK: • User personas • User journeys • UX considerations • Accessibility needs
|
||||
TOPIC: ${topic}
|
||||
CONTEXT: Target users: ${JSON.stringify(discoveryContext.seed_analysis.target_users)}
|
||||
EXPECTED: User experience analysis (personas, journeys, pain points)
|
||||
CONSTRAINTS: Focus on end-user perspective" --tool claude --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Wait for all 3 analyses to complete, then synthesize
|
||||
|
||||
// Generate product-brief.md with YAML frontmatter
|
||||
const brief = `---
|
||||
session_id: ${config.session_id}
|
||||
phase: 2
|
||||
document_type: product-brief
|
||||
status: draft
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
dependencies:
|
||||
- discovery-context.json
|
||||
- discuss-001-scope.md
|
||||
---
|
||||
|
||||
# Product Brief: ${topic}
|
||||
|
||||
## Vision
|
||||
${productPerspective.vision}
|
||||
|
||||
## Problem Statement
|
||||
${discoveryContext.seed_analysis.problem_statement}
|
||||
|
||||
## Target Users
|
||||
${personas.map(p => `### ${p.name}\n- **Role**: ${p.role}\n- **Pain Points**: ${p.painPoints}\n- **Goals**: ${p.goals}`).join('\n\n')}
|
||||
|
||||
## Goals & Success Metrics
|
||||
${productPerspective.goals}
|
||||
|
||||
## Scope
|
||||
### In Scope
|
||||
${productPerspective.inScope}
|
||||
|
||||
### Out of Scope
|
||||
${productPerspective.outOfScope}
|
||||
|
||||
## Technical Feasibility
|
||||
${technicalPerspective.summary}
|
||||
|
||||
## User Experience Considerations
|
||||
${userPerspective.summary}
|
||||
|
||||
## Multi-Perspective Synthesis
|
||||
### Convergent Themes
|
||||
${synthesis.convergent}
|
||||
|
||||
### Divergent Views
|
||||
${synthesis.divergent}
|
||||
|
||||
### Discussion Feedback Integration
|
||||
${discussionFeedback ? discussionFeedback.summary : 'N/A (first draft)'}
|
||||
|
||||
## Constraints
|
||||
${discoveryContext.seed_analysis.constraints.map(c => `- ${c}`).join('\n')}
|
||||
|
||||
## Open Questions
|
||||
${openQuestions.map(q => `- ${q}`).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/product-brief.md`, brief)
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-002: Requirements/PRD
|
||||
|
||||
```javascript
|
||||
async function generateRequirements(sessionFolder, config, priorDocs, discussionFeedback) {
|
||||
// Use Gemini CLI to expand requirements from product brief
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Generate functional and non-functional requirements from Product Brief.
|
||||
TASK:
|
||||
• Extract functional requirements (REQ-NNN format) with user stories and acceptance criteria
|
||||
• Generate non-functional requirements (NFR-{type}-NNN) for Performance/Security/Scalability/Usability
|
||||
• Apply MoSCoW prioritization (Must/Should/Could/Won't)
|
||||
• Create traceability matrix to Product Brief goals
|
||||
CONTEXT: Product Brief: ${priorDocs.productBrief}
|
||||
${discussionFeedback ? `DISCUSSION FEEDBACK: ${discussionFeedback}` : ''}
|
||||
EXPECTED: Structured requirements list in JSON
|
||||
CONSTRAINTS: Each requirement needs ID, title, user story, 2-4 acceptance criteria" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Generate requirements/ directory structure
|
||||
Bash(`mkdir -p ${sessionFolder}/requirements`)
|
||||
|
||||
// Write _index.md + individual REQ-*.md + NFR-*.md files
|
||||
// Following spec-generator templates/requirements-prd.md format
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-003: Architecture
|
||||
|
||||
```javascript
|
||||
async function generateArchitecture(sessionFolder, config, priorDocs, discussionFeedback) {
|
||||
// Generate architecture via Gemini
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Design system architecture based on requirements.
|
||||
TASK:
|
||||
• Select architecture style with justification
|
||||
• Define core components and responsibilities
|
||||
• Create component interaction diagram (Mermaid)
|
||||
• Choose tech stack (languages, frameworks, databases, infrastructure)
|
||||
• Generate 2-4 ADRs with alternatives and pros/cons
|
||||
• Design data model (Mermaid erDiagram)
|
||||
• Define security architecture
|
||||
CONTEXT: Requirements: ${priorDocs.requirementsIndex}
|
||||
Product Brief: ${priorDocs.productBrief}
|
||||
${discussionFeedback ? `DISCUSSION FEEDBACK: ${discussionFeedback}` : ''}
|
||||
EXPECTED: Complete architecture document
|
||||
CONSTRAINTS: Include ADRs with alternatives" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Challenge architecture via Codex
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Challenge and review proposed architecture.
|
||||
TASK: • Review ADR alternatives • Identify bottlenecks • Assess security gaps • Rate quality (1-5)
|
||||
CONTEXT: [architecture output from above]
|
||||
EXPECTED: Architecture review with ratings" --tool codex --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Generate architecture/ directory
|
||||
Bash(`mkdir -p ${sessionFolder}/architecture`)
|
||||
|
||||
// Write _index.md + ADR-*.md files
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-004: Epics & Stories
|
||||
|
||||
```javascript
|
||||
async function generateEpics(sessionFolder, config, priorDocs, discussionFeedback) {
|
||||
// Decompose via Gemini
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Decompose requirements into Epics and Stories.
|
||||
TASK:
|
||||
• Group 3-7 logical Epics by domain or user journey
|
||||
• Generate 2-5 Stories per Epic (user story format)
|
||||
• Create cross-Epic dependency map (Mermaid)
|
||||
• Define MVP scope with done criteria
|
||||
• Recommend execution order
|
||||
CONTEXT: Requirements: ${priorDocs.requirementsIndex}
|
||||
Architecture: ${priorDocs.architectureIndex}
|
||||
Product Brief: ${priorDocs.productBrief}
|
||||
${discussionFeedback ? `DISCUSSION FEEDBACK: ${discussionFeedback}` : ''}
|
||||
EXPECTED: Epic/Story decomposition with dependencies
|
||||
CONSTRAINTS: Each story needs AC, size estimate (S/M/L/XL), requirement tracing" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Generate epics/ directory
|
||||
Bash(`mkdir -p ${sessionFolder}/epics`)
|
||||
|
||||
// Write _index.md + EPIC-*.md files
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Self-Validation
|
||||
|
||||
```javascript
|
||||
// Validate generated document
|
||||
const validationChecks = {
|
||||
has_frontmatter: false,
|
||||
sections_complete: false,
|
||||
cross_references: false,
|
||||
discussion_integrated: false
|
||||
}
|
||||
|
||||
// Check YAML frontmatter
|
||||
const docContent = Read(`${sessionFolder}/${outputPath}`)
|
||||
validationChecks.has_frontmatter = /^---\n[\s\S]+?\n---/.test(docContent)
|
||||
|
||||
// Check required sections based on doc type
|
||||
const requiredSections = {
|
||||
'product-brief': ['Vision', 'Problem Statement', 'Target Users', 'Goals', 'Scope'],
|
||||
'requirements': ['_index.md', 'REQ-'],
|
||||
'architecture': ['_index.md', 'ADR-'],
|
||||
'epics': ['_index.md', 'EPIC-']
|
||||
}
|
||||
// Verify all sections present
|
||||
|
||||
// Check cross-references to prior documents
|
||||
validationChecks.cross_references = docContent.includes('session_id')
|
||||
|
||||
// Check discussion feedback integration
|
||||
validationChecks.discussion_integrated = !discussionFeedback || docContent.includes('Discussion')
|
||||
|
||||
const allValid = Object.values(validationChecks).every(v => v)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const docTypeLabel = {
|
||||
'product-brief': 'Product Brief',
|
||||
'requirements': 'Requirements/PRD',
|
||||
'architecture': 'Architecture Document',
|
||||
'epics': 'Epics & Stories'
|
||||
}
|
||||
|
||||
// Log before SendMessage
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "spec-writer", to: "coordinator",
|
||||
type: "draft_ready",
|
||||
summary: `${docTypeLabel[docType]} 完成: ${allValid ? '验证通过' : '部分验证失败'}`,
|
||||
ref: `${sessionFolder}/${outputPath}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 文档撰写结果
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**文档类型**: ${docTypeLabel[docType]}
|
||||
**验证状态**: ${allValid ? 'PASS' : 'PARTIAL'}
|
||||
|
||||
### 文档摘要
|
||||
${documentSummary}
|
||||
|
||||
### 讨论反馈整合
|
||||
${discussionFeedback ? '已整合前序讨论反馈' : '首次撰写(无前序讨论反馈)'}
|
||||
|
||||
### 自验证结果
|
||||
${Object.entries(validationChecks).map(([k, v]) => `- ${k}: ${v ? '✓' : '✗'}`).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
${sessionFolder}/${outputPath}
|
||||
|
||||
文档已就绪,可进入讨论轮次。`,
|
||||
summary: `${docTypeLabel[docType]} 就绪`
|
||||
})
|
||||
|
||||
// Mark task completed
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next DRAFT task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('DRAFT-') &&
|
||||
t.owner === 'spec-writer' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task -> back to Phase 1
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No DRAFT-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Prior document not found | Notify coordinator, request prerequisite |
|
||||
| CLI analysis failure | Retry with fallback tool, then direct generation |
|
||||
| Template sections incomplete | Generate best-effort, note gaps in report |
|
||||
| Discussion feedback contradicts prior docs | Note conflict in document, flag for next discussion |
|
||||
| Session folder missing | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
320
.claude/skills/team-lifecycle/SKILL.md
Normal file
320
.claude/skills/team-lifecycle/SKILL.md
Normal file
@@ -0,0 +1,320 @@
|
||||
---
|
||||
name: team-lifecycle
|
||||
description: Unified team skill for full lifecycle - spec/impl/test. All roles invoke this skill with --role arg for role-specific execution.
|
||||
allowed-tools: TeamCreate(*), TeamDelete(*), SendMessage(*), TaskCreate(*), TaskUpdate(*), TaskList(*), TaskGet(*), Task(*), AskUserQuestion(*), TodoWrite(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*)
|
||||
---
|
||||
|
||||
# Team Lifecycle
|
||||
|
||||
Unified team skill covering specification, implementation, testing, and review. All team members invoke this skill with `--role=xxx` to route to role-specific execution.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Skill(skill="team-lifecycle", args="--role=xxx") │
|
||||
└───────────────────┬─────────────────────────────┘
|
||||
│ Role Router
|
||||
┌───────┬───────┼───────┬───────┬───────┬───────┬───────┐
|
||||
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||||
┌─────────┐┌───────┐┌──────┐┌──────────┐┌───────┐┌────────┐┌──────┐┌────────┐
|
||||
│coordinator││analyst││writer││discussant││planner││executor││tester││reviewer│
|
||||
│ roles/ ││roles/ ││roles/││ roles/ ││roles/ ││ roles/ ││roles/││ roles/ │
|
||||
└─────────┘└───────┘└──────┘└──────────┘└───────┘└────────┘└──────┘└────────┘
|
||||
```
|
||||
|
||||
## Role Router
|
||||
|
||||
### Input Parsing
|
||||
|
||||
Parse `$ARGUMENTS` to extract `--role`:
|
||||
|
||||
```javascript
|
||||
const args = "$ARGUMENTS"
|
||||
const roleMatch = args.match(/--role[=\s]+(\w+)/)
|
||||
|
||||
if (!roleMatch) {
|
||||
throw new Error("Missing --role argument. Available roles: coordinator, analyst, writer, discussant, planner, executor, tester, reviewer")
|
||||
}
|
||||
|
||||
const role = roleMatch[1]
|
||||
const teamName = args.match(/--team[=\s]+([\w-]+)/)?.[1] || "lifecycle"
|
||||
```
|
||||
|
||||
### Role Dispatch
|
||||
|
||||
```javascript
|
||||
const VALID_ROLES = {
|
||||
"coordinator": { file: "roles/coordinator.md", prefix: null },
|
||||
"analyst": { file: "roles/analyst.md", prefix: "RESEARCH" },
|
||||
"writer": { file: "roles/writer.md", prefix: "DRAFT" },
|
||||
"discussant": { file: "roles/discussant.md", prefix: "DISCUSS" },
|
||||
"planner": { file: "roles/planner.md", prefix: "PLAN" },
|
||||
"executor": { file: "roles/executor.md", prefix: "IMPL" },
|
||||
"tester": { file: "roles/tester.md", prefix: "TEST" },
|
||||
"reviewer": { file: "roles/reviewer.md", prefix: ["REVIEW", "QUALITY"] }
|
||||
}
|
||||
|
||||
if (!VALID_ROLES[role]) {
|
||||
throw new Error(`Unknown role: ${role}. Available: ${Object.keys(VALID_ROLES).join(', ')}`)
|
||||
}
|
||||
|
||||
// Read and execute role-specific logic
|
||||
Read(VALID_ROLES[role].file)
|
||||
// → Execute the 5-phase process defined in that file
|
||||
```
|
||||
|
||||
### Available Roles
|
||||
|
||||
| Role | Task Prefix | Responsibility | Role File |
|
||||
|------|-------------|----------------|-----------|
|
||||
| `coordinator` | N/A | Pipeline orchestration, requirement clarification, task dispatch | [roles/coordinator.md](roles/coordinator.md) |
|
||||
| `analyst` | RESEARCH-* | Seed analysis, codebase exploration, context gathering | [roles/analyst.md](roles/analyst.md) |
|
||||
| `writer` | DRAFT-* | Product Brief / PRD / Architecture / Epics generation | [roles/writer.md](roles/writer.md) |
|
||||
| `discussant` | DISCUSS-* | Multi-perspective critique, consensus building | [roles/discussant.md](roles/discussant.md) |
|
||||
| `planner` | PLAN-* | Multi-angle exploration, structured planning | [roles/planner.md](roles/planner.md) |
|
||||
| `executor` | IMPL-* | Code implementation following plans | [roles/executor.md](roles/executor.md) |
|
||||
| `tester` | TEST-* | Adaptive test-fix cycles, quality gates | [roles/tester.md](roles/tester.md) |
|
||||
| `reviewer` | `REVIEW-*` + `QUALITY-*` | Code review + Spec quality validation (auto-switch by prefix) | [roles/reviewer.md](roles/reviewer.md) |
|
||||
|
||||
## Shared Infrastructure
|
||||
|
||||
### Message Bus (All Roles)
|
||||
|
||||
Every SendMessage **before**, must call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log",
|
||||
team: teamName,
|
||||
from: role,
|
||||
to: "coordinator",
|
||||
type: "<type>",
|
||||
summary: "<summary>",
|
||||
ref: "<file_path>"
|
||||
})
|
||||
```
|
||||
|
||||
**Message types by role**:
|
||||
|
||||
| Role | Types |
|
||||
|------|-------|
|
||||
| coordinator | `plan_approved`, `plan_revision`, `task_unblocked`, `fix_required`, `error`, `shutdown` |
|
||||
| analyst | `research_ready`, `research_progress`, `error` |
|
||||
| writer | `draft_ready`, `draft_revision`, `impl_progress`, `error` |
|
||||
| discussant | `discussion_ready`, `discussion_blocked`, `impl_progress`, `error` |
|
||||
| planner | `plan_ready`, `plan_revision`, `impl_progress`, `error` |
|
||||
| executor | `impl_complete`, `impl_progress`, `error` |
|
||||
| tester | `test_result`, `impl_progress`, `fix_required`, `error` |
|
||||
| reviewer | `review_result`, `quality_result`, `fix_required`, `error` |
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "${role}" --to "coordinator" --type "<type>" --summary "<summary>" --json`)
|
||||
Bash(`ccw team list --team "${teamName}" --last 10 --json`)
|
||||
Bash(`ccw team status --team "${teamName}" --json`)
|
||||
```
|
||||
|
||||
### Task Lifecycle (All Worker Roles)
|
||||
|
||||
```javascript
|
||||
// Standard task lifecycle every worker role follows
|
||||
// Phase 1: Discovery
|
||||
const tasks = TaskList()
|
||||
const prefixes = Array.isArray(VALID_ROLES[role].prefix) ? VALID_ROLES[role].prefix : [VALID_ROLES[role].prefix]
|
||||
const myTasks = tasks.filter(t =>
|
||||
prefixes.some(p => t.subject.startsWith(`${p}-`)) &&
|
||||
t.owner === role &&
|
||||
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-4: Role-specific (see roles/{role}.md)
|
||||
|
||||
// Phase 5: Report + Loop
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: role, to: "coordinator", type: "...", summary: "..." })
|
||||
SendMessage({ type: "message", recipient: "coordinator", content: "...", summary: "..." })
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
// Check for next task → back to Phase 1
|
||||
```
|
||||
|
||||
## Three-Mode Pipeline
|
||||
|
||||
```
|
||||
Spec-only:
|
||||
RESEARCH-001 → DISCUSS-001 → DRAFT-001 → DISCUSS-002
|
||||
→ DRAFT-002 → DISCUSS-003 → DRAFT-003 → DISCUSS-004
|
||||
→ DRAFT-004 → DISCUSS-005 → QUALITY-001 → DISCUSS-006
|
||||
|
||||
Impl-only:
|
||||
PLAN-001 → IMPL-001 → TEST-001 + REVIEW-001
|
||||
|
||||
Full-lifecycle:
|
||||
[Spec pipeline] → PLAN-001(blockedBy: DISCUSS-006) → IMPL-001 → TEST-001 + REVIEW-001
|
||||
```
|
||||
|
||||
## Coordinator Spawn Template
|
||||
|
||||
When coordinator creates teammates:
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Analyst (spec-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "analyst",
|
||||
prompt: `你是 team "${teamName}" 的 ANALYST。
|
||||
当你收到 RESEARCH-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=analyst") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 RESEARCH-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=analyst") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Writer (spec-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "writer",
|
||||
prompt: `你是 team "${teamName}" 的 WRITER。
|
||||
当你收到 DRAFT-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=writer") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 DRAFT-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=writer") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Discussant (spec-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "discussant",
|
||||
prompt: `你是 team "${teamName}" 的 DISCUSSANT。
|
||||
当你收到 DISCUSS-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=discussant") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
Session: ${sessionFolder}
|
||||
讨论深度: ${discussionDepth}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 DISCUSS-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=discussant") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Planner (impl-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "planner",
|
||||
prompt: `你是 team "${teamName}" 的 PLANNER。
|
||||
当你收到 PLAN-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=planner") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 PLAN-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=planner") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Executor (impl-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "executor",
|
||||
prompt: `你是 team "${teamName}" 的 EXECUTOR。
|
||||
当你收到 IMPL-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=executor") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 IMPL-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=executor") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Tester (impl-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "tester",
|
||||
prompt: `你是 team "${teamName}" 的 TESTER。
|
||||
当你收到 TEST-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=tester") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 TEST-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=tester") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
|
||||
// Reviewer (all modes)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
team_name: teamName,
|
||||
name: "reviewer",
|
||||
prompt: `你是 team "${teamName}" 的 REVIEWER。
|
||||
当你收到 REVIEW-* 或 QUALITY-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=reviewer") 执行。
|
||||
- REVIEW-* → 代码审查逻辑
|
||||
- QUALITY-* → 规格质量检查逻辑
|
||||
当前需求: ${taskDescription}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||
|
||||
工作流程:
|
||||
1. TaskList → 找到 REVIEW-* 或 QUALITY-* 任务
|
||||
2. Skill(skill="team-lifecycle", args="--role=reviewer") 执行
|
||||
3. team_msg log + SendMessage 结果给 coordinator
|
||||
4. TaskUpdate completed → 检查下一个任务`
|
||||
})
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| Unknown --role value | Error with available role list |
|
||||
| Missing --role arg | Error with usage hint |
|
||||
| Role file not found | Error with expected path |
|
||||
| Task prefix conflict | Log warning, proceed |
|
||||
207
.claude/skills/team-lifecycle/roles/analyst.md
Normal file
207
.claude/skills/team-lifecycle/roles/analyst.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Role: analyst
|
||||
|
||||
Seed analysis, codebase exploration, and multi-dimensional context gathering. Maps to spec-generator Phase 1 (Discovery).
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `analyst`
|
||||
- **Task Prefix**: `RESEARCH-*`
|
||||
- **Responsibility**: Seed Analysis → Codebase Exploration → Context Packaging → Report
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `research_ready` | analyst → coordinator | Research complete | With discovery-context.json path and dimension summary |
|
||||
| `research_progress` | analyst → coordinator | Long research progress | Intermediate progress update |
|
||||
| `error` | analyst → coordinator | Unrecoverable error | Codebase access failure, CLI timeout, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Research complete
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "research_ready", summary: "Research done: 5 exploration dimensions", ref: `${sessionFolder}/discovery-context.json` })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "error", summary: "Codebase access failed" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "analyst" --to "coordinator" --type "research_ready" --summary "Research done" --ref "${sessionFolder}/discovery-context.json" --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('RESEARCH-') &&
|
||||
t.owner === 'analyst' &&
|
||||
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: Seed Analysis
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : '.workflow/.spec-team/default'
|
||||
|
||||
// Parse topic from task description
|
||||
const topicLines = task.description.split('\n').filter(l => !l.startsWith('Session:') && !l.startsWith('输出:') && l.trim())
|
||||
const topic = topicLines[0] || task.subject.replace('RESEARCH-001: ', '')
|
||||
|
||||
// Use Gemini CLI for seed analysis
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Analyze the following topic/idea and extract structured seed information for specification generation.
|
||||
TASK:
|
||||
• Extract problem statement (what problem does this solve)
|
||||
• Identify target users and their pain points
|
||||
• Determine domain and industry context
|
||||
• List constraints and assumptions
|
||||
• Identify 3-5 exploration dimensions for deeper research
|
||||
• Assess complexity (simple/moderate/complex)
|
||||
|
||||
TOPIC: ${topic}
|
||||
|
||||
MODE: analysis
|
||||
CONTEXT: @**/*
|
||||
EXPECTED: JSON output with fields: problem_statement, target_users[], domain, constraints[], exploration_dimensions[], complexity_assessment
|
||||
CONSTRAINTS: Output as valid JSON" --tool gemini --mode analysis --rule analysis-analyze-technical-document`,
|
||||
run_in_background: true
|
||||
})
|
||||
// Wait for CLI result, then parse
|
||||
```
|
||||
|
||||
### Phase 3: Codebase Exploration (conditional)
|
||||
|
||||
```javascript
|
||||
// Check if there's an existing codebase to explore
|
||||
const hasProject = Bash(`test -f package.json || test -f Cargo.toml || test -f pyproject.toml || test -f go.mod; echo $?`)
|
||||
|
||||
if (hasProject === '0') {
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "analyst", to: "coordinator", type: "research_progress", summary: "种子分析完成, 开始代码库探索" })
|
||||
|
||||
// Explore codebase using ACE search
|
||||
const archSearch = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: `Architecture patterns, main modules, entry points for: ${topic}`
|
||||
})
|
||||
|
||||
// Detect tech stack from package files
|
||||
// Explore existing patterns and integration points
|
||||
|
||||
var codebaseContext = { tech_stack, architecture_patterns, existing_conventions, integration_points, constraints_from_codebase: [] }
|
||||
} else {
|
||||
var codebaseContext = null
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Context Packaging
|
||||
|
||||
```javascript
|
||||
// Generate spec-config.json
|
||||
const specConfig = {
|
||||
session_id: `SPEC-${topicSlug}-${dateStr}`,
|
||||
topic: topic,
|
||||
status: "research_complete",
|
||||
complexity: seedAnalysis.complexity_assessment || "moderate",
|
||||
phases_completed: ["discovery"],
|
||||
created_at: new Date().toISOString(),
|
||||
session_folder: sessionFolder,
|
||||
discussion_depth: task.description.match(/讨论深度:\s*(.+)/)?.[1] || "standard"
|
||||
}
|
||||
Write(`${sessionFolder}/spec-config.json`, JSON.stringify(specConfig, null, 2))
|
||||
|
||||
// Generate discovery-context.json
|
||||
const discoveryContext = {
|
||||
session_id: specConfig.session_id,
|
||||
phase: 1,
|
||||
document_type: "discovery-context",
|
||||
status: "complete",
|
||||
generated_at: new Date().toISOString(),
|
||||
seed_analysis: {
|
||||
problem_statement: seedAnalysis.problem_statement,
|
||||
target_users: seedAnalysis.target_users,
|
||||
domain: seedAnalysis.domain,
|
||||
constraints: seedAnalysis.constraints,
|
||||
exploration_dimensions: seedAnalysis.exploration_dimensions,
|
||||
complexity: seedAnalysis.complexity_assessment
|
||||
},
|
||||
codebase_context: codebaseContext,
|
||||
recommendations: { focus_areas: [], risks: [], open_questions: [] }
|
||||
}
|
||||
Write(`${sessionFolder}/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const dimensionCount = discoveryContext.seed_analysis.exploration_dimensions?.length || 0
|
||||
const hasCodebase = codebaseContext !== null
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "analyst", to: "coordinator",
|
||||
type: "research_ready",
|
||||
summary: `研究完成: ${dimensionCount}个探索维度, ${hasCodebase ? '有' : '无'}代码库上下文, 复杂度=${specConfig.complexity}`,
|
||||
ref: `${sessionFolder}/discovery-context.json`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 研究分析结果
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**复杂度**: ${specConfig.complexity}
|
||||
**代码库**: ${hasCodebase ? '已检测到现有项目' : '全新项目'}
|
||||
|
||||
### 问题陈述
|
||||
${discoveryContext.seed_analysis.problem_statement}
|
||||
|
||||
### 目标用户
|
||||
${(discoveryContext.seed_analysis.target_users || []).map(u => '- ' + u).join('\n')}
|
||||
|
||||
### 探索维度
|
||||
${(discoveryContext.seed_analysis.exploration_dimensions || []).map((d, i) => (i+1) + '. ' + d).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- Config: ${sessionFolder}/spec-config.json
|
||||
- Context: ${sessionFolder}/discovery-context.json
|
||||
|
||||
研究已就绪,可进入讨论轮次 DISCUSS-001。`,
|
||||
summary: `研究就绪: ${dimensionCount}维度, ${specConfig.complexity}`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next RESEARCH task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No RESEARCH-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Gemini CLI analysis failure | Fallback to direct Claude analysis without CLI |
|
||||
| Codebase detection failed | Continue as new project (no codebase context) |
|
||||
| Session folder cannot be created | Notify coordinator, request alternative path |
|
||||
| Topic too vague for analysis | Report to coordinator with clarification questions |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
326
.claude/skills/team-lifecycle/roles/coordinator.md
Normal file
326
.claude/skills/team-lifecycle/roles/coordinator.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Role: coordinator
|
||||
|
||||
Team lifecycle coordinator. Orchestrates the full pipeline across three modes: spec-only, impl-only, and full-lifecycle. Handles requirement clarification, team creation, task chain management, cross-phase coordination, and result reporting.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `coordinator`
|
||||
- **Task Prefix**: N/A (coordinator creates tasks, doesn't receive them)
|
||||
- **Responsibility**: Orchestration
|
||||
- **Communication**: SendMessage to all teammates
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `plan_approved` | coordinator → planner | Plan reviewed and accepted | Planner can mark task completed |
|
||||
| `plan_revision` | coordinator → planner | Plan needs changes | Feedback with required changes |
|
||||
| `task_unblocked` | coordinator → any | Dependency resolved | Notify worker of available task |
|
||||
| `fix_required` | coordinator → executor/writer | Review/Quality found issues | Create fix task |
|
||||
| `error` | coordinator → all | Critical system error | Escalation to user |
|
||||
| `shutdown` | coordinator → all | Team being dissolved | Clean shutdown signal |
|
||||
|
||||
## Execution
|
||||
|
||||
### Phase 1: Requirement Clarification
|
||||
|
||||
Parse `$ARGUMENTS` to extract `--team-name` and task description.
|
||||
|
||||
```javascript
|
||||
const args = "$ARGUMENTS"
|
||||
const teamNameMatch = args.match(/--team-name[=\s]+([\w-]+)/)
|
||||
const teamName = teamNameMatch ? teamNameMatch[1] : `lifecycle-${Date.now().toString(36)}`
|
||||
const taskDescription = args.replace(/--team-name[=\s]+[\w-]+/, '').replace(/--role[=\s]+\w+/, '').trim()
|
||||
```
|
||||
|
||||
Use AskUserQuestion to collect mode and constraints:
|
||||
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "选择工作模式:",
|
||||
header: "Mode",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "spec-only", description: "仅生成规格文档(研究→讨论→撰写→质量检查)" },
|
||||
{ label: "impl-only", description: "仅实现代码(规划→实现→测试+审查)" },
|
||||
{ label: "full-lifecycle", description: "完整生命周期(规格→实现→测试+审查)" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "MVP 范围:",
|
||||
header: "Scope",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "最小可行", description: "核心功能优先" },
|
||||
{ label: "功能完整", description: "覆盖主要用例" },
|
||||
{ label: "全面实现", description: "包含边缘场景和优化" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Spec/Full 模式追加收集
|
||||
if (mode === 'spec-only' || mode === 'full-lifecycle') {
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "重点领域:",
|
||||
header: "Focus",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "产品定义", description: "聚焦用户需求和产品定位" },
|
||||
{ label: "技术架构", description: "聚焦技术选型和系统设计" },
|
||||
{ label: "全面规格", description: "均衡覆盖产品+技术" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "讨论深度:",
|
||||
header: "Depth",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "快速共识", description: "每轮讨论简短聚焦,快速推进" },
|
||||
{ label: "深度讨论", description: "每轮多视角深入分析" },
|
||||
{ label: "全面辩论", description: "4个维度全覆盖,严格共识门控" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Simple tasks can skip clarification.
|
||||
|
||||
### Phase 2: Create Team + Spawn Workers
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Session setup
|
||||
const topicSlug = taskDescription.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const specSessionFolder = `.workflow/.spec-team/${topicSlug}-${dateStr}`
|
||||
const implSessionFolder = `.workflow/.team-plan/${topicSlug}-${dateStr}`
|
||||
|
||||
if (mode === 'spec-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p ${specSessionFolder}/discussions`)
|
||||
}
|
||||
if (mode === 'impl-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p ${implSessionFolder}`)
|
||||
}
|
||||
```
|
||||
|
||||
**Conditional spawn based on mode** (see SKILL.md Coordinator Spawn Template for full prompts):
|
||||
|
||||
| Mode | Spawned Workers |
|
||||
|------|-----------------|
|
||||
| spec-only | analyst, writer, discussant, reviewer (4) |
|
||||
| impl-only | planner, executor, tester, reviewer (4) |
|
||||
| full-lifecycle | analyst, writer, discussant, planner, executor, tester, reviewer (7) |
|
||||
|
||||
Each worker receives a prompt that tells it to invoke `Skill(skill="team-lifecycle", args="--role=<name>")` when receiving tasks.
|
||||
|
||||
### Phase 3: Create Task Chain
|
||||
|
||||
Task chain creation depends on the selected mode.
|
||||
|
||||
#### Spec-only Task Chain
|
||||
|
||||
```javascript
|
||||
// RESEARCH Phase
|
||||
TaskCreate({ subject: "RESEARCH-001: 主题发现与上下文研究", description: `${taskDescription}\n\nSession: ${specSessionFolder}\n输出: ${specSessionFolder}/spec-config.json + discovery-context.json`, activeForm: "研究中" })
|
||||
TaskUpdate({ taskId: researchId, owner: "analyst" })
|
||||
|
||||
// DISCUSS-001: 范围讨论 (blockedBy RESEARCH-001)
|
||||
TaskCreate({ subject: "DISCUSS-001: 研究结果讨论 - 范围确认与方向调整", description: `讨论 RESEARCH-001 的发现结果\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/discovery-context.json\n输出: ${specSessionFolder}/discussions/discuss-001-scope.md\n\n讨论维度: 范围确认、方向调整、风险预判、探索缺口`, activeForm: "讨论范围中" })
|
||||
TaskUpdate({ taskId: discuss1Id, owner: "discussant", addBlockedBy: [researchId] })
|
||||
|
||||
// DRAFT-001: Product Brief (blockedBy DISCUSS-001)
|
||||
TaskCreate({ subject: "DRAFT-001: 撰写 Product Brief", description: `基于研究和讨论共识撰写产品简报\n\nSession: ${specSessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${specSessionFolder}/product-brief.md\n\n使用多视角分析: 产品/技术/用户`, activeForm: "撰写 Brief 中" })
|
||||
TaskUpdate({ taskId: draft1Id, owner: "writer", addBlockedBy: [discuss1Id] })
|
||||
|
||||
// DISCUSS-002: Brief 评审 (blockedBy DRAFT-001)
|
||||
TaskCreate({ subject: "DISCUSS-002: Product Brief 多视角评审", description: `评审 Product Brief 文档\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/product-brief.md\n输出: ${specSessionFolder}/discussions/discuss-002-brief.md\n\n讨论维度: 产品定位、目标用户、成功指标、竞品差异`, activeForm: "评审 Brief 中" })
|
||||
TaskUpdate({ taskId: discuss2Id, owner: "discussant", addBlockedBy: [draft1Id] })
|
||||
|
||||
// DRAFT-002: Requirements/PRD (blockedBy DISCUSS-002)
|
||||
TaskCreate({ subject: "DRAFT-002: 撰写 Requirements/PRD", description: `基于 Brief 和讨论反馈撰写需求文档\n\nSession: ${specSessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${specSessionFolder}/requirements/\n\n包含: 功能需求(REQ-*) + 非功能需求(NFR-*) + MoSCoW 优先级`, activeForm: "撰写 PRD 中" })
|
||||
TaskUpdate({ taskId: draft2Id, owner: "writer", addBlockedBy: [discuss2Id] })
|
||||
|
||||
// DISCUSS-003: 需求完整性 (blockedBy DRAFT-002)
|
||||
TaskCreate({ subject: "DISCUSS-003: 需求完整性与优先级讨论", description: `讨论 PRD 需求完整性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/requirements/_index.md\n输出: ${specSessionFolder}/discussions/discuss-003-requirements.md\n\n讨论维度: 需求遗漏、MoSCoW合理性、验收标准可测性、非功能需求充分性`, activeForm: "讨论需求中" })
|
||||
TaskUpdate({ taskId: discuss3Id, owner: "discussant", addBlockedBy: [draft2Id] })
|
||||
|
||||
// DRAFT-003: Architecture (blockedBy DISCUSS-003)
|
||||
TaskCreate({ subject: "DRAFT-003: 撰写 Architecture Document", description: `基于需求和讨论反馈撰写架构文档\n\nSession: ${specSessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${specSessionFolder}/architecture/\n\n包含: 架构风格 + 组件图 + 技术选型 + ADR-* + 数据模型`, activeForm: "撰写架构中" })
|
||||
TaskUpdate({ taskId: draft3Id, owner: "writer", addBlockedBy: [discuss3Id] })
|
||||
|
||||
// DISCUSS-004: 技术可行性 (blockedBy DRAFT-003)
|
||||
TaskCreate({ subject: "DISCUSS-004: 架构决策与技术可行性讨论", description: `讨论架构设计合理性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/architecture/_index.md\n输出: ${specSessionFolder}/discussions/discuss-004-architecture.md\n\n讨论维度: 技术选型风险、可扩展性、安全架构、ADR替代方案`, activeForm: "讨论架构中" })
|
||||
TaskUpdate({ taskId: discuss4Id, owner: "discussant", addBlockedBy: [draft3Id] })
|
||||
|
||||
// DRAFT-004: Epics & Stories (blockedBy DISCUSS-004)
|
||||
TaskCreate({ subject: "DRAFT-004: 撰写 Epics & Stories", description: `基于架构和讨论反馈撰写史诗和用户故事\n\nSession: ${specSessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${specSessionFolder}/epics/\n\n包含: EPIC-* + STORY-* + 依赖图 + MVP定义 + 执行顺序`, activeForm: "撰写 Epics 中" })
|
||||
TaskUpdate({ taskId: draft4Id, owner: "writer", addBlockedBy: [discuss4Id] })
|
||||
|
||||
// DISCUSS-005: 执行就绪 (blockedBy DRAFT-004)
|
||||
TaskCreate({ subject: "DISCUSS-005: 执行计划与MVP范围讨论", description: `讨论执行计划就绪性\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/epics/_index.md\n输出: ${specSessionFolder}/discussions/discuss-005-epics.md\n\n讨论维度: Epic粒度、故事估算、MVP范围、执行顺序、依赖风险`, activeForm: "讨论执行计划中" })
|
||||
TaskUpdate({ taskId: discuss5Id, owner: "discussant", addBlockedBy: [draft4Id] })
|
||||
|
||||
// QUALITY-001: Readiness Check (blockedBy DISCUSS-005)
|
||||
TaskCreate({ subject: "QUALITY-001: 规格就绪度检查", description: `全文档交叉验证和质量评分\n\nSession: ${specSessionFolder}\n输入: 全部文档\n输出: ${specSessionFolder}/readiness-report.md + spec-summary.md\n\n评分维度: 完整性(25%) + 一致性(25%) + 可追溯性(25%) + 深度(25%)`, activeForm: "质量检查中" })
|
||||
TaskUpdate({ taskId: qualityId, owner: "reviewer", addBlockedBy: [discuss5Id] })
|
||||
|
||||
// DISCUSS-006: 最终签收 (blockedBy QUALITY-001)
|
||||
TaskCreate({ subject: "DISCUSS-006: 最终签收与交付确认", description: `最终讨论和签收\n\nSession: ${specSessionFolder}\n输入: ${specSessionFolder}/readiness-report.md\n输出: ${specSessionFolder}/discussions/discuss-006-final.md\n\n讨论维度: 质量报告审查、遗留问题处理、交付确认、下一步建议`, activeForm: "最终签收讨论中" })
|
||||
TaskUpdate({ taskId: discuss6Id, owner: "discussant", addBlockedBy: [qualityId] })
|
||||
```
|
||||
|
||||
#### Impl-only Task Chain
|
||||
|
||||
```javascript
|
||||
// PLAN-001
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\n写入: ${implSessionFolder}/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner" })
|
||||
|
||||
// IMPL-001 (blockedBy PLAN-001)
|
||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nPlan: ${implSessionFolder}/plan.json`, activeForm: "实现中" })
|
||||
TaskUpdate({ taskId: implId, owner: "executor", addBlockedBy: [planId] })
|
||||
|
||||
// TEST-001 (blockedBy IMPL-001)
|
||||
TaskCreate({ subject: "TEST-001: 测试修复循环", description: `${taskDescription}`, activeForm: "测试中" })
|
||||
TaskUpdate({ taskId: testId, owner: "tester", addBlockedBy: [implId] })
|
||||
|
||||
// REVIEW-001 (blockedBy IMPL-001, parallel with TEST-001)
|
||||
TaskCreate({ subject: "REVIEW-001: 代码审查与需求验证", description: `${taskDescription}\n\nPlan: ${implSessionFolder}/plan.json`, activeForm: "审查中" })
|
||||
TaskUpdate({ taskId: reviewId, owner: "reviewer", addBlockedBy: [implId] })
|
||||
```
|
||||
|
||||
#### Full-lifecycle Task Chain
|
||||
|
||||
Create both spec and impl chains, with PLAN-001 blockedBy DISCUSS-006:
|
||||
|
||||
```javascript
|
||||
// [All spec-only tasks as above]
|
||||
// Then:
|
||||
TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDescription}\n\nSpec: ${specSessionFolder}\n写入: ${implSessionFolder}/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner", addBlockedBy: [discuss6Id] })
|
||||
// [Rest of impl-only tasks as above]
|
||||
```
|
||||
|
||||
### Phase 4: Coordination Loop
|
||||
|
||||
Receive teammate messages and make dispatch decisions. **Before each decision: `team_msg list` to review recent messages. After each decision: `team_msg log` to record.**
|
||||
|
||||
#### Spec Messages
|
||||
|
||||
| Received Message | Action |
|
||||
|-----------------|--------|
|
||||
| Analyst: research_ready | Read discovery-context.json → team_msg log → TaskUpdate RESEARCH completed (auto-unblocks DISCUSS-001) |
|
||||
| Discussant: discussion_ready | Read discussion.md → judge if revision needed → unblock next DRAFT task |
|
||||
| Discussant: discussion_blocked | Intervene → AskUserQuestion for user decision → write decision to discussion record → manually unblock |
|
||||
| Writer: draft_ready | Read document summary → team_msg log → TaskUpdate DRAFT completed (auto-unblocks next DISCUSS) |
|
||||
| Writer: draft_revision | Update dependencies → unblock related discussion tasks |
|
||||
| Reviewer: quality_result (PASS ≥80%) | team_msg log → TaskUpdate QUALITY completed (auto-unblocks DISCUSS-006) |
|
||||
| Reviewer: quality_result (REVIEW 60-79%) | team_msg log → notify writer of improvement suggestions |
|
||||
| Reviewer: fix_required (FAIL <60%) | Create DRAFT-fix task → assign writer |
|
||||
|
||||
#### Impl Messages
|
||||
|
||||
| Received Message | Action |
|
||||
|-----------------|--------|
|
||||
| Planner: plan_ready | Read plan → approve/request revision → team_msg log(plan_approved/plan_revision) → TaskUpdate + SendMessage |
|
||||
| Executor: impl_complete | team_msg log(task_unblocked) → TaskUpdate IMPL completed (auto-unblocks TEST + REVIEW) |
|
||||
| Tester: test_result ≥ 95% | team_msg log → TaskUpdate TEST completed |
|
||||
| Tester: test_result < 95% + iterations > 5 | team_msg log(error) → escalate to user |
|
||||
| Reviewer: review_result (no critical) | team_msg log → TaskUpdate REVIEW completed |
|
||||
| Reviewer: review_result (has critical) | team_msg log(fix_required) → TaskCreate IMPL-fix → assign executor |
|
||||
| All tasks completed | → Phase 5 |
|
||||
|
||||
#### Full-lifecycle Handoff
|
||||
|
||||
When DISCUSS-006 completes in full-lifecycle mode, PLAN-001 is auto-unblocked via the dependency chain.
|
||||
|
||||
#### Discussion Blocked Handling
|
||||
|
||||
```javascript
|
||||
if (msgType === 'discussion_blocked') {
|
||||
const blockReason = msg.data.reason
|
||||
const options = msg.data.options
|
||||
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: `讨论 ${msg.ref} 遇到分歧: ${blockReason}\n请选择方向:`,
|
||||
header: "Decision",
|
||||
multiSelect: false,
|
||||
options: options.map(opt => ({ label: opt.label, description: opt.description }))
|
||||
}]
|
||||
})
|
||||
// Write user decision to discussion record, then unblock next task
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Report + Persistent Loop
|
||||
|
||||
Summarize results based on mode:
|
||||
- **spec-only**: Document inventory, quality scores, discussion rounds
|
||||
- **impl-only**: Changed files, test pass rate, review verdict
|
||||
- **full-lifecycle**: Both spec summary + impl summary
|
||||
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "当前需求已完成。下一步:",
|
||||
header: "Next",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "新需求", description: "提交新需求给当前团队" },
|
||||
{ label: "交付执行", description: "将规格交给执行 workflow(仅 spec 模式)" },
|
||||
{ label: "关闭团队", description: "关闭所有 teammate 并清理" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
// 新需求 → 回到 Phase 1(复用 team,新建任务链)
|
||||
// 交付执行 → 提示可用的执行 workflow
|
||||
// 关闭 → shutdown 给每个 teammate → TeamDelete()
|
||||
```
|
||||
|
||||
## Session File Structure
|
||||
|
||||
```
|
||||
# Spec session
|
||||
.workflow/.spec-team/{topic-slug}-{YYYY-MM-DD}/
|
||||
├── spec-config.json
|
||||
├── discovery-context.json
|
||||
├── product-brief.md
|
||||
├── requirements/
|
||||
├── architecture/
|
||||
├── epics/
|
||||
├── readiness-report.md
|
||||
├── spec-summary.md
|
||||
└── discussions/
|
||||
└── discuss-001..006.md
|
||||
|
||||
# Impl session
|
||||
.workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-*.json
|
||||
├── explorations-manifest.json
|
||||
├── planning-context.md
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| Teammate 无响应 | 发追踪消息,2次无响应 → 重新 spawn |
|
||||
| Plan 被拒 3+ 次 | Coordinator 自行规划 |
|
||||
| 测试卡在 <80% 超 5 次迭代 | 上报用户 |
|
||||
| Review 发现 critical | 创建 IMPL-fix 任务给 executor |
|
||||
| 讨论无法共识 | Coordinator 介入 → AskUserQuestion |
|
||||
| 文档质量 <60% | 创建 DRAFT-fix 任务给 writer |
|
||||
| Writer 修订 3+ 次 | 上报用户,建议调整范围 |
|
||||
| Research 无法完成 | 降级为简化模式 |
|
||||
223
.claude/skills/team-lifecycle/roles/discussant.md
Normal file
223
.claude/skills/team-lifecycle/roles/discussant.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Role: discussant
|
||||
|
||||
Multi-perspective critique, consensus building, and conflict escalation. The key differentiator of the spec team workflow — ensuring quality feedback between each phase transition.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `discussant`
|
||||
- **Task Prefix**: `DISCUSS-*`
|
||||
- **Responsibility**: Load Artifact → Multi-Perspective Critique → Synthesize Consensus → Report
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `discussion_ready` | discussant → coordinator | Discussion complete, consensus reached | With discussion record path and decision summary |
|
||||
| `discussion_blocked` | discussant → coordinator | Cannot reach consensus | With divergence points and options, needs coordinator |
|
||||
| `impl_progress` | discussant → coordinator | Long discussion progress | Multi-perspective analysis progress |
|
||||
| `error` | discussant → coordinator | Discussion cannot proceed | Input artifact missing, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Discussion complete
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "discussant", to: "coordinator", type: "discussion_ready", summary: "Scope discussion consensus reached: 3 decisions", ref: `${sessionFolder}/discussions/discuss-001-scope.md` })
|
||||
|
||||
// Discussion blocked
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "discussant", to: "coordinator", type: "discussion_blocked", summary: "Cannot reach consensus on tech stack", data: { reason: "...", options: [...] } })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "discussant", to: "coordinator", type: "error", summary: "Input artifact missing" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "discussant" --to "coordinator" --type "discussion_ready" --summary "Discussion complete" --ref "${sessionFolder}/discussions/discuss-001-scope.md" --json`)
|
||||
```
|
||||
|
||||
## Discussion Dimension Model
|
||||
|
||||
Each discussion round analyzes from 4 perspectives:
|
||||
|
||||
| Perspective | Focus | Representative |
|
||||
|-------------|-------|----------------|
|
||||
| **Product** | Market fit, user value, business viability, competitive differentiation | Product Manager |
|
||||
| **Technical** | Feasibility, tech debt, performance, security, maintainability | Tech Lead |
|
||||
| **Quality** | Completeness, testability, consistency, standards compliance | QA Lead |
|
||||
| **Risk** | Risk identification, dependency analysis, assumption validation, failure modes | Risk Analyst |
|
||||
|
||||
## Discussion Round Configuration
|
||||
|
||||
| Round | Artifact | Key Perspectives | Focus |
|
||||
|-------|----------|-----------------|-------|
|
||||
| DISCUSS-001 | discovery-context | product + risk | Scope confirmation, direction |
|
||||
| DISCUSS-002 | product-brief | product + technical + quality | Positioning, feasibility |
|
||||
| DISCUSS-003 | requirements | quality + product | Completeness, priority |
|
||||
| DISCUSS-004 | architecture | technical + risk | Tech choices, security |
|
||||
| DISCUSS-005 | epics | product + technical + quality | MVP scope, estimation |
|
||||
| DISCUSS-006 | readiness-report | all 4 perspectives | Final sign-off |
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('DISCUSS-') &&
|
||||
t.owner === 'discussant' &&
|
||||
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: Artifact Loading
|
||||
|
||||
```javascript
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
const roundMatch = task.subject.match(/DISCUSS-(\d+)/)
|
||||
const roundNumber = roundMatch ? parseInt(roundMatch[1]) : 0
|
||||
|
||||
const roundConfig = {
|
||||
1: { artifact: 'discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk'], label: '范围讨论' },
|
||||
2: { artifact: 'product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality'], label: 'Brief评审' },
|
||||
3: { artifact: 'requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product'], label: '需求讨论' },
|
||||
4: { artifact: 'architecture/_index.md', type: 'md', outputFile: 'discuss-004-architecture.md', perspectives: ['technical', 'risk'], label: '架构讨论' },
|
||||
5: { artifact: 'epics/_index.md', type: 'md', outputFile: 'discuss-005-epics.md', perspectives: ['product', 'technical', 'quality'], label: 'Epics讨论' },
|
||||
6: { artifact: 'readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk'], label: '最终签收' }
|
||||
}
|
||||
|
||||
const config = roundConfig[roundNumber]
|
||||
// Load target artifact and prior discussion records for continuity
|
||||
Bash(`mkdir -p ${sessionFolder}/discussions`)
|
||||
```
|
||||
|
||||
### Phase 3: Multi-Perspective Critique
|
||||
|
||||
Launch parallel CLI analyses for each required perspective:
|
||||
|
||||
- **Product Perspective** (gemini): Market fit, user value, business viability, competitive differentiation. Rate 1-5 with improvement suggestions.
|
||||
- **Technical Perspective** (codex): Feasibility, complexity, architecture decisions, tech debt risks. Rate 1-5.
|
||||
- **Quality Perspective** (claude): Completeness, testability, consistency, ambiguity detection. Rate 1-5.
|
||||
- **Risk Perspective** (gemini): Risk identification, dependency analysis, assumption validation, failure modes. Rate risk level.
|
||||
|
||||
Each CLI call produces structured critique with: strengths[], weaknesses[], suggestions[], rating.
|
||||
|
||||
### Phase 4: Consensus Synthesis
|
||||
|
||||
```javascript
|
||||
const synthesis = {
|
||||
convergent_themes: [],
|
||||
divergent_views: [],
|
||||
action_items: [],
|
||||
open_questions: [],
|
||||
decisions: [],
|
||||
risk_flags: [],
|
||||
overall_sentiment: '', // positive/neutral/concerns/critical
|
||||
consensus_reached: true // false if major unresolvable conflicts
|
||||
}
|
||||
|
||||
// Extract convergent themes (items mentioned positively by 2+ perspectives)
|
||||
// Extract divergent views (items where perspectives conflict)
|
||||
// Check for unresolvable conflicts
|
||||
const criticalDivergences = synthesis.divergent_views.filter(d => d.severity === 'high')
|
||||
if (criticalDivergences.length > 0) synthesis.consensus_reached = false
|
||||
|
||||
// Determine overall sentiment from average rating
|
||||
// Generate discussion record markdown with all perspectives, convergence, divergence, action items
|
||||
|
||||
Write(`${sessionFolder}/discussions/${config.outputFile}`, discussionRecord)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
if (synthesis.consensus_reached) {
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "discussant", to: "coordinator",
|
||||
type: "discussion_ready",
|
||||
summary: `${config.label}讨论完成: ${synthesis.action_items.length}个行动项, ${synthesis.open_questions.length}个开放问题, 总体${synthesis.overall_sentiment}`,
|
||||
ref: `${sessionFolder}/discussions/${config.outputFile}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 讨论结果: ${config.label}
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**共识**: 已达成
|
||||
**总体评价**: ${synthesis.overall_sentiment}
|
||||
|
||||
### 行动项 (${synthesis.action_items.length})
|
||||
${synthesis.action_items.map((item, i) => (i+1) + '. ' + item).join('\n') || '无'}
|
||||
|
||||
### 开放问题 (${synthesis.open_questions.length})
|
||||
${synthesis.open_questions.map((q, i) => (i+1) + '. ' + q).join('\n') || '无'}
|
||||
|
||||
### 讨论记录
|
||||
${sessionFolder}/discussions/${config.outputFile}
|
||||
|
||||
共识已达成,可推进至下一阶段。`,
|
||||
summary: `${config.label}共识达成: ${synthesis.action_items.length}行动项`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Consensus blocked - escalate to coordinator
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "discussant", to: "coordinator",
|
||||
type: "discussion_blocked",
|
||||
summary: `${config.label}讨论阻塞: ${criticalDivergences.length}个关键分歧需决策`,
|
||||
data: {
|
||||
reason: criticalDivergences.map(d => d.description).join('; '),
|
||||
options: criticalDivergences.map(d => ({ label: d.topic, description: d.options?.join(' vs ') || d.description }))
|
||||
}
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 讨论阻塞: ${config.label}
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**状态**: 无法达成共识,需要 coordinator 介入
|
||||
|
||||
### 关键分歧
|
||||
${criticalDivergences.map((d, i) => (i+1) + '. **' + d.topic + '**: ' + d.description).join('\n\n')}
|
||||
|
||||
请通过 AskUserQuestion 收集用户对分歧点的决策。`,
|
||||
summary: `${config.label}阻塞: ${criticalDivergences.length}分歧`
|
||||
})
|
||||
// Keep task in_progress, wait for coordinator resolution
|
||||
}
|
||||
|
||||
// Check for next DISCUSS task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No DISCUSS-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Target artifact not found | Notify coordinator, request prerequisite completion |
|
||||
| CLI perspective analysis failure | Fallback to direct Claude analysis for that perspective |
|
||||
| All CLI analyses fail | Generate basic discussion from direct reading |
|
||||
| Consensus timeout (all perspectives diverge) | Escalate as discussion_blocked |
|
||||
| Prior discussion records missing | Continue without continuity context |
|
||||
| Session folder not found | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
235
.claude/skills/team-lifecycle/roles/executor.md
Normal file
235
.claude/skills/team-lifecycle/roles/executor.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Role: executor
|
||||
|
||||
Code implementation following approved plans. Reads plan files, implements changes, self-validates, and reports completion.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `executor`
|
||||
- **Task Prefix**: `IMPL-*`
|
||||
- **Responsibility**: Load plan → Implement code → Self-validate → Report completion
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `impl_complete` | executor → coordinator | All implementation complete | With changed files list and acceptance status |
|
||||
| `impl_progress` | executor → coordinator | Batch/subtask completed | Progress percentage and completed subtask |
|
||||
| `error` | executor → coordinator | Blocking problem | Plan file missing, file conflict, sub-agent failure |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Progress update
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_progress", summary: "Batch 1/3 done: auth middleware implemented", data: { batch: 1, total: 3, files: ["src/middleware/auth.ts"] } })
|
||||
|
||||
// Implementation complete
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_complete", summary: "IMPL-001 complete: 5 files changed, all acceptance met", data: { changedFiles: 5, syntaxClean: true } })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "error", summary: "Invalid plan.json path, cannot load implementation plan" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "executor" --to "coordinator" --type "impl_complete" --summary "IMPL-001 complete: 5 files changed" --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task & Plan Loading
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('IMPL-') &&
|
||||
t.owner === 'executor' &&
|
||||
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' })
|
||||
|
||||
// Extract plan path from task description
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
const planPath = planPathMatch ? planPathMatch[0] : null
|
||||
|
||||
if (!planPath) {
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "error", summary: "plan.json路径无效" })
|
||||
SendMessage({ type: "message", recipient: "coordinator", content: `Cannot find plan.json in ${task.subject}`, summary: "Plan path not found" })
|
||||
return
|
||||
}
|
||||
|
||||
const plan = JSON.parse(Read(planPath))
|
||||
```
|
||||
|
||||
### Phase 2: Task Grouping
|
||||
|
||||
```javascript
|
||||
// Extract dependencies and group into parallel/sequential batches
|
||||
function createBatches(planTasks) {
|
||||
const processed = new Set()
|
||||
const batches = []
|
||||
|
||||
// Phase 1: Independent tasks → single parallel batch
|
||||
const independent = planTasks.filter(t => (t.depends_on || []).length === 0)
|
||||
if (independent.length > 0) {
|
||||
independent.forEach(t => processed.add(t.id))
|
||||
batches.push({ type: 'parallel', tasks: independent })
|
||||
}
|
||||
|
||||
// Phase 2+: Dependent tasks in topological order
|
||||
let remaining = planTasks.filter(t => !processed.has(t.id))
|
||||
while (remaining.length > 0) {
|
||||
const ready = remaining.filter(t => (t.depends_on || []).every(d => processed.has(d)))
|
||||
if (ready.length === 0) break // circular dependency guard
|
||||
ready.forEach(t => processed.add(t.id))
|
||||
batches.push({ type: ready.length > 1 ? 'parallel' : 'sequential', tasks: ready })
|
||||
remaining = remaining.filter(t => !processed.has(t.id))
|
||||
}
|
||||
return batches
|
||||
}
|
||||
|
||||
// Load task files from .task/ directory
|
||||
const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${planPath.replace('plan.json', '')}.task/${id}.json`)))
|
||||
const batches = createBatches(planTasks)
|
||||
```
|
||||
|
||||
### Phase 3: Code Implementation
|
||||
|
||||
```javascript
|
||||
// Unified Task Prompt Builder
|
||||
function buildExecutionPrompt(planTask) {
|
||||
return `
|
||||
## ${planTask.title}
|
||||
|
||||
**Scope**: \`${planTask.scope}\` | **Action**: ${planTask.action || 'implement'}
|
||||
|
||||
### Files
|
||||
${(planTask.files || []).map(f => `- **${f.path}** → \`${f.target}\`: ${f.change}`).join('\n')}
|
||||
|
||||
### How to do it
|
||||
${planTask.description}
|
||||
|
||||
${(planTask.implementation || []).map(step => `- ${step}`).join('\n')}
|
||||
|
||||
### Reference
|
||||
- Pattern: ${planTask.reference?.pattern || 'N/A'}
|
||||
- Files: ${planTask.reference?.files?.join(', ') || 'N/A'}
|
||||
|
||||
### Done when
|
||||
${(planTask.convergence?.criteria || []).map(c => `- [ ] ${c}`).join('\n')}
|
||||
`
|
||||
}
|
||||
|
||||
const changedFiles = []
|
||||
|
||||
for (const batch of batches) {
|
||||
if (batch.tasks.length === 1 && isSimpleTask(batch.tasks[0])) {
|
||||
// Simple task: direct file editing
|
||||
const t = batch.tasks[0]
|
||||
for (const f of (t.files || [])) {
|
||||
const content = Read(f.path)
|
||||
Edit({ file_path: f.path, old_string: "...", new_string: "..." })
|
||||
changedFiles.push(f.path)
|
||||
}
|
||||
} else {
|
||||
// Complex task(s): delegate to code-developer sub-agent
|
||||
const prompt = batch.tasks.map(buildExecutionPrompt).join('\n\n---\n')
|
||||
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: batch.tasks.map(t => t.title).join(' | '),
|
||||
prompt: `## Goal\n${plan.summary}\n\n## Tasks\n${prompt}\n\n## Context\n### Project Guidelines\n@.workflow/project-guidelines.json\n\nComplete each task according to its "Done when" checklist.`
|
||||
})
|
||||
|
||||
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||
}
|
||||
|
||||
// Progress update
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_progress", summary: `Batch完成: ${changedFiles.length}个文件已变更` })
|
||||
}
|
||||
|
||||
function isSimpleTask(task) {
|
||||
return (task.files || []).length <= 2 && (task.risks || []).length === 0
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Self-Validation
|
||||
|
||||
```javascript
|
||||
// Syntax check
|
||||
const syntaxResult = Bash(`tsc --noEmit 2>&1 || true`)
|
||||
const hasSyntaxErrors = syntaxResult.includes('error TS')
|
||||
if (hasSyntaxErrors) { /* attempt auto-fix */ }
|
||||
|
||||
// Verify acceptance criteria
|
||||
const acceptanceStatus = planTasks.map(t => ({
|
||||
title: t.title,
|
||||
criteria: (t.convergence?.criteria || []).map(c => ({ criterion: c, met: true }))
|
||||
}))
|
||||
|
||||
// Run affected tests (if identifiable)
|
||||
const testFiles = changedFiles
|
||||
.map(f => f.replace(/\/src\//, '/tests/').replace(/\.(ts|js)$/, '.test.$1'))
|
||||
.filter(f => Bash(`test -f ${f} && echo exists || true`).includes('exists'))
|
||||
if (testFiles.length > 0) Bash(`npx jest ${testFiles.join(' ')} --passWithNoTests 2>&1 || true`)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "executor", to: "coordinator",
|
||||
type: "impl_complete",
|
||||
summary: `IMPL完成: ${[...new Set(changedFiles)].length}个文件变更, syntax=${hasSyntaxErrors ? 'errors' : 'clean'}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Implementation Complete
|
||||
|
||||
**Task**: ${task.subject}
|
||||
|
||||
### Changed Files
|
||||
${[...new Set(changedFiles)].map(f => '- ' + f).join('\n')}
|
||||
|
||||
### Acceptance Criteria
|
||||
${acceptanceStatus.map(t => '**' + t.title + '**: ' + (t.criteria.every(c => c.met) ? 'All met' : 'Partial')).join('\n')}
|
||||
|
||||
### Validation
|
||||
- Syntax: ${hasSyntaxErrors ? 'Has errors (attempted fix)' : 'Clean'}
|
||||
- Tests: ${testFiles.length > 0 ? 'Ran' : 'N/A'}
|
||||
|
||||
Implementation is ready for testing and review.`,
|
||||
summary: `IMPL complete: ${[...new Set(changedFiles)].length} files changed`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next IMPL task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No IMPL-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Plan file not found | Notify coordinator, request plan location |
|
||||
| Syntax errors after implementation | Attempt auto-fix, report remaining errors |
|
||||
| Sub-agent failure | Retry once, then attempt direct implementation |
|
||||
| File conflict / merge issue | Notify coordinator, request guidance |
|
||||
| Test failures in self-validation | Report in completion message, let tester handle |
|
||||
| Circular dependencies in plan | Execute in plan order, ignore dependency chain |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
274
.claude/skills/team-lifecycle/roles/planner.md
Normal file
274
.claude/skills/team-lifecycle/roles/planner.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Role: planner
|
||||
|
||||
Multi-angle code exploration and structured implementation planning. Submits plans to the coordinator for approval.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `planner`
|
||||
- **Task Prefix**: `PLAN-*`
|
||||
- **Responsibility**: Code exploration → Implementation planning → Coordinator approval
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `plan_ready` | planner → coordinator | Plan generation complete | With plan.json path and task count summary |
|
||||
| `plan_revision` | planner → coordinator | Plan revised and resubmitted | Describes changes made |
|
||||
| `impl_progress` | planner → coordinator | Exploration phase progress | Optional, for long explorations |
|
||||
| `error` | planner → coordinator | Unrecoverable error | Exploration failure, schema missing, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Plan ready
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_ready", summary: "Plan ready: 3 tasks, Medium complexity", ref: `${sessionFolder}/plan.json` })
|
||||
|
||||
// Plan revision
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "plan_revision", summary: "Split task-2 into two subtasks per feedback" })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "planner", to: "coordinator", type: "error", summary: "plan-overview-base-schema.json not found, using default structure" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "planner" --to "coordinator" --type "plan_ready" --summary "Plan ready: 3 tasks" --ref "${sessionFolder}/plan.json" --json`)
|
||||
```
|
||||
|
||||
## 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: Multi-Angle Exploration
|
||||
|
||||
```javascript
|
||||
// Session setup
|
||||
const taskSlug = task.subject.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const dateStr = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||
const sessionFolder = `.workflow/.team-plan/${taskSlug}-${dateStr}`
|
||||
Bash(`mkdir -p ${sessionFolder}`)
|
||||
|
||||
// Complexity assessment
|
||||
function assessComplexity(desc) {
|
||||
let score = 0
|
||||
if (/refactor|architect|restructure|模块|系统/.test(desc)) score += 2
|
||||
if (/multiple|多个|across|跨/.test(desc)) score += 2
|
||||
if (/integrate|集成|api|database/.test(desc)) score += 1
|
||||
if (/security|安全|performance|性能/.test(desc)) score += 1
|
||||
return score >= 4 ? 'High' : score >= 2 ? 'Medium' : 'Low'
|
||||
}
|
||||
|
||||
const complexity = assessComplexity(task.description)
|
||||
|
||||
// Angle selection based on task type
|
||||
const ANGLE_PRESETS = {
|
||||
architecture: ['architecture', 'dependencies', 'modularity', 'integration-points'],
|
||||
security: ['security', 'auth-patterns', 'dataflow', 'validation'],
|
||||
performance: ['performance', 'bottlenecks', 'caching', 'data-access'],
|
||||
bugfix: ['error-handling', 'dataflow', 'state-management', 'edge-cases'],
|
||||
feature: ['patterns', 'integration-points', 'testing', 'dependencies']
|
||||
}
|
||||
|
||||
function selectAngles(desc, count) {
|
||||
const text = desc.toLowerCase()
|
||||
let preset = 'feature'
|
||||
if (/refactor|architect|restructure|modular/.test(text)) preset = 'architecture'
|
||||
else if (/security|auth|permission|access/.test(text)) preset = 'security'
|
||||
else if (/performance|slow|optimi|cache/.test(text)) preset = 'performance'
|
||||
else if (/fix|bug|error|issue|broken/.test(text)) preset = 'bugfix'
|
||||
return ANGLE_PRESETS[preset].slice(0, count)
|
||||
}
|
||||
|
||||
const angleCount = complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1)
|
||||
const selectedAngles = selectAngles(task.description, angleCount)
|
||||
|
||||
// Execute exploration
|
||||
if (complexity === 'Low') {
|
||||
// Direct exploration via semantic search
|
||||
const results = mcp__ace-tool__search_context({
|
||||
project_root_path: projectRoot,
|
||||
query: task.description
|
||||
})
|
||||
Write(`${sessionFolder}/exploration-${selectedAngles[0]}.json`, JSON.stringify({
|
||||
project_structure: "...",
|
||||
relevant_files: [],
|
||||
patterns: [],
|
||||
dependencies: [],
|
||||
integration_points: [],
|
||||
constraints: [],
|
||||
clarification_needs: [],
|
||||
_metadata: { exploration_angle: selectedAngles[0] }
|
||||
}, null, 2))
|
||||
} else {
|
||||
// Launch parallel cli-explore-agent for each angle
|
||||
selectedAngles.forEach((angle, index) => {
|
||||
Task({
|
||||
subagent_type: "cli-explore-agent",
|
||||
run_in_background: false,
|
||||
description: `Explore: ${angle}`,
|
||||
prompt: `
|
||||
## Task Objective
|
||||
Execute **${angle}** exploration for task planning context.
|
||||
|
||||
## Output Location
|
||||
**Session Folder**: ${sessionFolder}
|
||||
**Output File**: ${sessionFolder}/exploration-${angle}.json
|
||||
|
||||
## Assigned Context
|
||||
- **Exploration Angle**: ${angle}
|
||||
- **Task Description**: ${task.description}
|
||||
- **Exploration Index**: ${index + 1} of ${selectedAngles.length}
|
||||
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Run: rg -l "{relevant_keyword}" --type ts (locate relevant files)
|
||||
2. Execute: cat ~/.ccw/workflows/cli-templates/schemas/explore-json-schema.json (get output schema)
|
||||
3. Read: .workflow/project-tech.json (if exists - technology stack)
|
||||
|
||||
## Expected Output
|
||||
Write JSON to: ${sessionFolder}/exploration-${angle}.json
|
||||
Follow explore-json-schema.json structure with ${angle}-focused findings.
|
||||
|
||||
**MANDATORY**: Every file in relevant_files MUST have:
|
||||
- **rationale** (required): Specific selection basis tied to ${angle} topic (>10 chars, not generic)
|
||||
- **role** (required): modify_target|dependency|pattern_reference|test_target|type_definition|integration_point|config|context_only
|
||||
- **discovery_source** (recommended): bash-scan|cli-analysis|ace-search|dependency-trace|manual
|
||||
- **key_symbols** (recommended): Key functions/classes/types relevant to task
|
||||
`
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Build explorations manifest
|
||||
const explorationManifest = {
|
||||
session_id: `${taskSlug}-${dateStr}`,
|
||||
task_description: task.description,
|
||||
complexity: complexity,
|
||||
exploration_count: selectedAngles.length,
|
||||
explorations: selectedAngles.map(angle => ({
|
||||
angle: angle,
|
||||
file: `exploration-${angle}.json`,
|
||||
path: `${sessionFolder}/exploration-${angle}.json`
|
||||
}))
|
||||
}
|
||||
Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2))
|
||||
```
|
||||
|
||||
### Phase 3: Plan Generation
|
||||
|
||||
```javascript
|
||||
// Read schema reference
|
||||
const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`)
|
||||
|
||||
if (complexity === 'Low') {
|
||||
// Direct Claude planning
|
||||
Bash(`mkdir -p ${sessionFolder}/.task`)
|
||||
// Generate plan.json + .task/TASK-*.json following schemas
|
||||
} else {
|
||||
// Use cli-lite-planning-agent for Medium/High
|
||||
Task({
|
||||
subagent_type: "cli-lite-planning-agent",
|
||||
run_in_background: false,
|
||||
description: "Generate detailed implementation plan",
|
||||
prompt: `Generate implementation plan.
|
||||
Output: ${sessionFolder}/plan.json + ${sessionFolder}/.task/TASK-*.json
|
||||
Schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json
|
||||
Task Description: ${task.description}
|
||||
Explorations: ${explorationManifest}
|
||||
Complexity: ${complexity}
|
||||
Requirements: 2-7 tasks, each with id, title, files[].change, convergence.criteria, depends_on`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Submit for Approval
|
||||
|
||||
```javascript
|
||||
const plan = JSON.parse(Read(`${sessionFolder}/plan.json`))
|
||||
const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${sessionFolder}/.task/${id}.json`)))
|
||||
const taskCount = plan.task_count || plan.task_ids.length
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "planner", to: "coordinator",
|
||||
type: "plan_ready",
|
||||
summary: `Plan就绪: ${taskCount}个task, ${complexity}复杂度`,
|
||||
ref: `${sessionFolder}/plan.json`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Plan Ready for Review
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Complexity**: ${complexity}
|
||||
**Tasks**: ${taskCount}
|
||||
|
||||
### Task Summary
|
||||
${planTasks.map((t, i) => (i+1) + '. ' + t.title).join('\n')}
|
||||
|
||||
### Approach
|
||||
${plan.approach}
|
||||
|
||||
### Plan Location
|
||||
${sessionFolder}/plan.json
|
||||
Task Files: ${sessionFolder}/.task/
|
||||
|
||||
Please review and approve or request revisions.`,
|
||||
summary: `Plan ready: ${taskCount} tasks`
|
||||
})
|
||||
|
||||
// Wait for coordinator response (approve → mark completed, revision → update and resubmit)
|
||||
```
|
||||
|
||||
### Phase 5: After Approval
|
||||
|
||||
```javascript
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next PLAN task → back to Phase 1
|
||||
```
|
||||
|
||||
## Session Files
|
||||
|
||||
```
|
||||
.workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── planning-context.md
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No PLAN-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Exploration agent failure | Skip exploration, plan from task description only |
|
||||
| Planning agent failure | Fallback to direct Claude planning |
|
||||
| Plan rejected 3+ times | Notify coordinator, suggest alternative approach |
|
||||
| Schema file not found | Use basic plan structure without schema validation |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
508
.claude/skills/team-lifecycle/roles/reviewer.md
Normal file
508
.claude/skills/team-lifecycle/roles/reviewer.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Role: reviewer
|
||||
|
||||
Unified review role handling both code review (REVIEW-*) and specification quality checks (QUALITY-*). Auto-switches behavior based on task prefix.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `reviewer`
|
||||
- **Task Prefix**: `REVIEW-*` + `QUALITY-*`
|
||||
- **Responsibility**: Discover Task → Branch by Prefix → Review/Score → Report
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `review_result` | reviewer → coordinator | Code review complete | With verdict (APPROVE/CONDITIONAL/BLOCK) and findings |
|
||||
| `quality_result` | reviewer → coordinator | Spec quality check complete | With score and gate decision (PASS/REVIEW/FAIL) |
|
||||
| `fix_required` | reviewer → coordinator | Critical issues found | Needs IMPL-fix or DRAFT-fix tasks |
|
||||
| `error` | reviewer → coordinator | Review cannot proceed | Plan missing, documents missing, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Code review result
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "review_result", summary: "REVIEW APPROVE: 8 findings (critical=0, high=2)", data: { verdict: "APPROVE", critical: 0, high: 2 } })
|
||||
|
||||
// Spec quality result
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "quality_result", summary: "Quality check PASS: 85.0 score", data: { gate: "PASS", score: 85.0 } })
|
||||
|
||||
// Fix required
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "fix_required", summary: "Critical security issues found, IMPL-fix needed", data: { critical: 2 } })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "error", summary: "plan.json not found, cannot verify requirements" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "reviewer" --to "coordinator" --type "review_result" --summary "REVIEW APPROVE: 8 findings" --data '{"verdict":"APPROVE","critical":0}' --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery (Dual-Prefix)
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
(t.subject.startsWith('REVIEW-') || t.subject.startsWith('QUALITY-')) &&
|
||||
t.owner === 'reviewer' &&
|
||||
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' })
|
||||
|
||||
// Determine review mode
|
||||
const reviewMode = task.subject.startsWith('REVIEW-') ? 'code' : 'spec'
|
||||
```
|
||||
|
||||
### Phase 2: Context Loading (Branch by Mode)
|
||||
|
||||
**Code Review Mode (REVIEW-*):**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
// Load plan for acceptance criteria
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
let plan = null
|
||||
if (planPathMatch) {
|
||||
try { plan = JSON.parse(Read(planPathMatch[0])) } catch {}
|
||||
}
|
||||
|
||||
// Get changed files via git
|
||||
const changedFiles = Bash(`git diff --name-only HEAD~1 2>/dev/null || git diff --name-only --cached`)
|
||||
.split('\n').filter(f => f.trim() && !f.startsWith('.'))
|
||||
|
||||
// Read changed file contents (limit to 20 files)
|
||||
const fileContents = {}
|
||||
for (const file of changedFiles.slice(0, 20)) {
|
||||
try { fileContents[file] = Read(file) } catch {}
|
||||
}
|
||||
|
||||
// Load test results if available
|
||||
const testSummary = tasks.find(t => t.subject.startsWith('TEST-') && t.status === 'completed')
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality Mode (QUALITY-*):**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load all spec documents
|
||||
const documents = {
|
||||
config: null, discoveryContext: null, productBrief: null,
|
||||
requirementsIndex: null, requirements: [], architectureIndex: null,
|
||||
adrs: [], epicsIndex: null, epics: [], discussions: []
|
||||
}
|
||||
|
||||
try { documents.config = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/epics/_index.md`) } catch {}
|
||||
|
||||
// Load individual documents
|
||||
Glob({ pattern: `${sessionFolder}/requirements/REQ-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/requirements/NFR-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/architecture/ADR-*.md` }).forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/epics/EPIC-*.md` }).forEach(f => { try { documents.epics.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/discussions/discuss-*.md` }).forEach(f => { try { documents.discussions.push(Read(f)) } catch {} })
|
||||
|
||||
const docInventory = {
|
||||
config: !!documents.config, discoveryContext: !!documents.discoveryContext,
|
||||
productBrief: !!documents.productBrief, requirements: documents.requirements.length > 0,
|
||||
architecture: documents.adrs.length > 0, epics: documents.epics.length > 0,
|
||||
discussions: documents.discussions.length
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Review Execution (Branch by Mode)
|
||||
|
||||
**Code Review — 4-Dimension Analysis:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
const findings = { critical: [], high: [], medium: [], low: [] }
|
||||
|
||||
// Quality: @ts-ignore, any, console.log, empty catch
|
||||
const qualityIssues = reviewQuality(changedFiles)
|
||||
|
||||
// Security: eval/exec/innerHTML, hardcoded secrets, SQL injection, XSS
|
||||
const securityIssues = reviewSecurity(changedFiles)
|
||||
|
||||
// Architecture: circular deps, large files, layering violations
|
||||
const architectureIssues = reviewArchitecture(changedFiles, fileContents)
|
||||
|
||||
// Requirement Verification: plan acceptance criteria vs implementation
|
||||
const requirementIssues = plan ? verifyRequirements(plan, fileContents) : []
|
||||
|
||||
const allIssues = [...qualityIssues, ...securityIssues, ...architectureIssues, ...requirementIssues]
|
||||
allIssues.forEach(issue => findings[issue.severity].push(issue))
|
||||
|
||||
// Verdict determination
|
||||
const hasCritical = findings.critical.length > 0
|
||||
const verdict = hasCritical ? 'BLOCK' : findings.high.length > 3 ? 'CONDITIONAL' : 'APPROVE'
|
||||
}
|
||||
```
|
||||
|
||||
Review dimension functions:
|
||||
|
||||
```javascript
|
||||
function reviewQuality(files) {
|
||||
const issues = []
|
||||
const tsIgnore = Grep({ pattern: '@ts-ignore|@ts-expect-error', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (tsIgnore) issues.push({ type: 'quality', detail: '@ts-ignore/@ts-expect-error usage', severity: 'medium' })
|
||||
const anyType = Grep({ pattern: ': any[^A-Z]|as any', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (anyType) issues.push({ type: 'quality', detail: 'Untyped `any` usage', severity: 'medium' })
|
||||
const consoleLogs = Grep({ pattern: 'console\\.log', glob: '*.{ts,tsx,js,jsx}', path: 'src/', output_mode: 'content' })
|
||||
if (consoleLogs) issues.push({ type: 'quality', detail: 'console.log in source code', severity: 'low' })
|
||||
const emptyCatch = Grep({ pattern: 'catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content', multiline: true })
|
||||
if (emptyCatch) issues.push({ type: 'quality', detail: 'Empty catch blocks', severity: 'high' })
|
||||
return issues
|
||||
}
|
||||
|
||||
function reviewSecurity(files) {
|
||||
const issues = []
|
||||
const dangerousFns = Grep({ pattern: '\\beval\\b|\\bexec\\b|innerHTML|dangerouslySetInnerHTML', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (dangerousFns) issues.push({ type: 'security', detail: 'Dangerous function: eval/exec/innerHTML', severity: 'critical' })
|
||||
const secrets = Grep({ pattern: 'password\\s*=\\s*["\']|secret\\s*=\\s*["\']|api_key\\s*=\\s*["\']', glob: '*.{ts,tsx,js,jsx,py}', output_mode: 'content', '-i': true })
|
||||
if (secrets) issues.push({ type: 'security', detail: 'Hardcoded secrets/passwords', severity: 'critical' })
|
||||
const sqlInjection = Grep({ pattern: 'query\\s*\\(\\s*`|execute\\s*\\(\\s*`', glob: '*.{ts,js,py}', output_mode: 'content', '-i': true })
|
||||
if (sqlInjection) issues.push({ type: 'security', detail: 'Potential SQL injection via template literals', severity: 'critical' })
|
||||
const xssRisk = Grep({ pattern: 'document\\.write|window\\.location\\s*=', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (xssRisk) issues.push({ type: 'security', detail: 'Potential XSS vectors', severity: 'high' })
|
||||
return issues
|
||||
}
|
||||
|
||||
function reviewArchitecture(files, fileContents) {
|
||||
const issues = []
|
||||
for (const [file, content] of Object.entries(fileContents)) {
|
||||
const imports = content.match(/from\s+['"]([^'"]+)['"]/g) || []
|
||||
if (imports.filter(i => i.includes('../..')).length > 2) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: excessive parent imports (layering violation)`, severity: 'medium' })
|
||||
}
|
||||
if (content.split('\n').length > 500) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: ${content.split('\n').length} lines - consider splitting`, severity: 'low' })
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
function verifyRequirements(plan, fileContents) {
|
||||
const issues = []
|
||||
for (const planTask of (plan.tasks || [])) {
|
||||
for (const criterion of (planTask.acceptance || [])) {
|
||||
const keywords = criterion.toLowerCase().split(/\s+/).filter(w => w.length > 4)
|
||||
const hasEvidence = keywords.some(kw => Object.values(fileContents).some(c => c.toLowerCase().includes(kw)))
|
||||
if (!hasEvidence) {
|
||||
issues.push({ type: 'requirement', detail: `Acceptance criterion may not be met: "${criterion}" (${planTask.title})`, severity: 'high' })
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality — 4-Dimension Scoring:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
const scores = { completeness: 0, consistency: 0, traceability: 0, depth: 0 }
|
||||
|
||||
// Completeness (25%): all sections present with content
|
||||
function scoreCompleteness(docs) {
|
||||
let score = 0
|
||||
const checks = [
|
||||
{ name: 'spec-config.json', present: !!docs.config, weight: 5 },
|
||||
{ name: 'discovery-context.json', present: !!docs.discoveryContext, weight: 10 },
|
||||
{ name: 'product-brief.md', present: !!docs.productBrief, weight: 20 },
|
||||
{ name: 'requirements/_index.md', present: !!docs.requirementsIndex, weight: 15 },
|
||||
{ name: 'REQ-* files', present: docs.requirements.length > 0, weight: 10 },
|
||||
{ name: 'architecture/_index.md', present: !!docs.architectureIndex, weight: 15 },
|
||||
{ name: 'ADR-* files', present: docs.adrs.length > 0, weight: 10 },
|
||||
{ name: 'epics/_index.md', present: !!docs.epicsIndex, weight: 10 },
|
||||
{ name: 'EPIC-* files', present: docs.epics.length > 0, weight: 5 }
|
||||
]
|
||||
checks.forEach(c => { if (c.present) score += c.weight })
|
||||
return { score, issues: checks.filter(c => !c.present).map(c => `Missing: ${c.name}`) }
|
||||
}
|
||||
|
||||
// Consistency (25%): terminology, format, references
|
||||
function scoreConsistency(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
const sessionId = docs.config?.session_id
|
||||
if (sessionId && docs.productBrief && !docs.productBrief.includes(sessionId)) {
|
||||
score -= 15; issues.push('Product Brief missing session_id reference')
|
||||
}
|
||||
const docsWithFM = [docs.productBrief, docs.requirementsIndex, docs.architectureIndex, docs.epicsIndex].filter(Boolean)
|
||||
const hasFM = docsWithFM.map(d => /^---\n[\s\S]+?\n---/.test(d))
|
||||
if (!hasFM.every(v => v === hasFM[0])) {
|
||||
score -= 20; issues.push('Inconsistent YAML frontmatter across documents')
|
||||
}
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
// Traceability (25%): goals → reqs → arch → stories chain
|
||||
function scoreTraceability(docs) {
|
||||
let score = 0
|
||||
const issues = []
|
||||
if (docs.productBrief && docs.requirementsIndex) {
|
||||
if (docs.requirements.some(r => /goal|brief|vision/i.test(r))) score += 25
|
||||
else issues.push('Requirements lack references to Product Brief goals')
|
||||
}
|
||||
if (docs.requirementsIndex && docs.architectureIndex) {
|
||||
if (docs.adrs.some(a => /REQ-|requirement/i.test(a))) score += 25
|
||||
else issues.push('Architecture ADRs lack requirement references')
|
||||
}
|
||||
if (docs.requirementsIndex && docs.epicsIndex) {
|
||||
if (docs.epics.some(e => /REQ-|requirement/i.test(e))) score += 25
|
||||
else issues.push('Epics/Stories lack requirement tracing')
|
||||
}
|
||||
if (score >= 50) score += 25
|
||||
return { score: Math.min(100, score), issues }
|
||||
}
|
||||
|
||||
// Depth (25%): AC testable, ADRs justified, stories estimable
|
||||
function scoreDepth(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
if (!docs.requirements.some(r => /acceptance|criteria|验收/i.test(r) && r.length > 200)) {
|
||||
score -= 25; issues.push('Acceptance criteria may lack specificity')
|
||||
}
|
||||
if (docs.adrs.length > 0 && !docs.adrs.some(a => /alternative|替代|pros|cons/i.test(a))) {
|
||||
score -= 25; issues.push('ADRs lack alternatives analysis')
|
||||
}
|
||||
if (docs.epics.length > 0 && !docs.epics.some(e => /\b[SMLX]{1,2}\b|Small|Medium|Large/.test(e))) {
|
||||
score -= 25; issues.push('Stories lack size estimates')
|
||||
}
|
||||
if (![docs.architectureIndex, docs.epicsIndex].some(d => d && /```mermaid/.test(d))) {
|
||||
score -= 10; issues.push('Missing Mermaid diagrams')
|
||||
}
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
const completenessResult = scoreCompleteness(documents)
|
||||
const consistencyResult = scoreConsistency(documents)
|
||||
const traceabilityResult = scoreTraceability(documents)
|
||||
const depthResult = scoreDepth(documents)
|
||||
|
||||
scores.completeness = completenessResult.score
|
||||
scores.consistency = consistencyResult.score
|
||||
scores.traceability = traceabilityResult.score
|
||||
scores.depth = depthResult.score
|
||||
|
||||
const overallScore = (scores.completeness + scores.consistency + scores.traceability + scores.depth) / 4
|
||||
const qualityGate = overallScore >= 80 ? 'PASS' : overallScore >= 60 ? 'REVIEW' : 'FAIL'
|
||||
const allSpecIssues = [...completenessResult.issues, ...consistencyResult.issues, ...traceabilityResult.issues, ...depthResult.issues]
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Report Generation (Branch by Mode)
|
||||
|
||||
**Code Review — Generate Recommendations:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
const totalIssues = Object.values(findings).flat().length
|
||||
const recommendations = []
|
||||
if (hasCritical) recommendations.push('Fix all critical security issues before merging')
|
||||
if (findings.high.length > 0) recommendations.push('Address high severity issues in a follow-up')
|
||||
if (findings.medium.length > 3) recommendations.push('Consider refactoring to reduce medium severity issues')
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality — Generate Reports:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
// Generate readiness-report.md
|
||||
const readinessReport = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: readiness-report
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Readiness Report
|
||||
|
||||
## Quality Scores
|
||||
| Dimension | Score | Weight |
|
||||
|-----------|-------|--------|
|
||||
| Completeness | ${scores.completeness}% | 25% |
|
||||
| Consistency | ${scores.consistency}% | 25% |
|
||||
| Traceability | ${scores.traceability}% | 25% |
|
||||
| Depth | ${scores.depth}% | 25% |
|
||||
| **Overall** | **${overallScore.toFixed(1)}%** | **100%** |
|
||||
|
||||
## Quality Gate: ${qualityGate}
|
||||
|
||||
## Issues Found
|
||||
${allSpecIssues.map(i => '- ' + i).join('\n') || 'None'}
|
||||
|
||||
## Document Inventory
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (v === true ? '✓' : v === false ? '✗' : v)).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/readiness-report.md`, readinessReport)
|
||||
|
||||
// Generate spec-summary.md
|
||||
const specSummary = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: spec-summary
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Specification Summary
|
||||
|
||||
**Topic**: ${documents.config?.topic || 'N/A'}
|
||||
**Complexity**: ${documents.config?.complexity || 'N/A'}
|
||||
**Quality Score**: ${overallScore.toFixed(1)}% (${qualityGate})
|
||||
**Discussion Rounds**: ${documents.discussions.length}
|
||||
|
||||
## Key Deliverables
|
||||
- Product Brief: ${docInventory.productBrief ? '✓' : '✗'}
|
||||
- Requirements (PRD): ${docInventory.requirements ? '✓ (' + documents.requirements.length + ' items)' : '✗'}
|
||||
- Architecture: ${docInventory.architecture ? '✓ (' + documents.adrs.length + ' ADRs)' : '✗'}
|
||||
- Epics & Stories: ${docInventory.epics ? '✓ (' + documents.epics.length + ' epics)' : '✗'}
|
||||
|
||||
## Next Steps
|
||||
${qualityGate === 'PASS' ? '- Ready for handoff to execution workflows' :
|
||||
qualityGate === 'REVIEW' ? '- Address review items, then proceed to execution' :
|
||||
'- Fix critical issues before proceeding'}
|
||||
`
|
||||
Write(`${sessionFolder}/spec-summary.md`, specSummary)
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator (Branch by Mode)
|
||||
|
||||
**Code Review Report:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "reviewer", to: "coordinator",
|
||||
type: hasCritical ? "fix_required" : "review_result",
|
||||
summary: `REVIEW ${verdict}: ${totalIssues}个发现 (critical=${findings.critical.length}, high=${findings.high.length})`,
|
||||
data: { verdict, critical: findings.critical.length, high: findings.high.length, medium: findings.medium.length, low: findings.low.length }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Code Review Report
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Verdict**: ${verdict}
|
||||
**Files Reviewed**: ${changedFiles.length}
|
||||
**Total Findings**: ${totalIssues}
|
||||
|
||||
### Finding Summary
|
||||
- Critical: ${findings.critical.length}
|
||||
- High: ${findings.high.length}
|
||||
- Medium: ${findings.medium.length}
|
||||
- Low: ${findings.low.length}
|
||||
|
||||
${findings.critical.length > 0 ? '### Critical Issues\n' + findings.critical.map(f => '- [' + f.type.toUpperCase() + '] ' + f.detail).join('\n') + '\n' : ''}
|
||||
${findings.high.length > 0 ? '### High Severity\n' + findings.high.map(f => '- [' + f.type.toUpperCase() + '] ' + f.detail).join('\n') + '\n' : ''}
|
||||
### Recommendations
|
||||
${recommendations.map(r => '- ' + r).join('\n')}
|
||||
|
||||
${plan ? '### Requirement Verification\n' + (plan.tasks || []).map(t => '- **' + t.title + '**: ' + (requirementIssues.filter(i => i.detail.includes(t.title)).length === 0 ? 'Met' : 'Needs verification')).join('\n') : ''}`,
|
||||
summary: `Review: ${verdict} (${totalIssues} findings)`
|
||||
})
|
||||
|
||||
if (!hasCritical) {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
}
|
||||
// If critical, keep in_progress for coordinator to create fix tasks
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality Report:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "reviewer", to: "coordinator",
|
||||
type: qualityGate === 'FAIL' ? "fix_required" : "quality_result",
|
||||
summary: `质量检查 ${qualityGate}: ${overallScore.toFixed(1)}分 (完整性${scores.completeness}/一致性${scores.consistency}/追溯${scores.traceability}/深度${scores.depth})`,
|
||||
data: { gate: qualityGate, score: overallScore, issues: allSpecIssues }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 质量审查报告
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**总分**: ${overallScore.toFixed(1)}%
|
||||
**Gate**: ${qualityGate}
|
||||
|
||||
### 评分详情
|
||||
| 维度 | 分数 |
|
||||
|------|------|
|
||||
| 完整性 | ${scores.completeness}% |
|
||||
| 一致性 | ${scores.consistency}% |
|
||||
| 可追溯性 | ${scores.traceability}% |
|
||||
| 深度 | ${scores.depth}% |
|
||||
|
||||
### 问题列表 (${allSpecIssues.length})
|
||||
${allSpecIssues.map(i => '- ' + i).join('\n') || '无问题'}
|
||||
|
||||
### 文档清单
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (typeof v === 'boolean' ? (v ? '✓' : '✗') : v)).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- 就绪报告: ${sessionFolder}/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec-summary.md
|
||||
|
||||
${qualityGate === 'PASS' ? '质量达标,可进入最终讨论轮次 DISCUSS-006。' :
|
||||
qualityGate === 'REVIEW' ? '质量基本达标但有改进空间,建议在讨论中审查。' :
|
||||
'质量未达标,建议创建 DRAFT-fix 任务修复关键问题。'}`,
|
||||
summary: `质量 ${qualityGate}: ${overallScore.toFixed(1)}分`
|
||||
})
|
||||
|
||||
if (qualityGate !== 'FAIL') {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
}
|
||||
}
|
||||
|
||||
// Check for next REVIEW-* or QUALITY-* task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No REVIEW-*/QUALITY-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Plan file not found (code review) | Review without requirement verification, note in report |
|
||||
| No changed files detected | Report to coordinator, may need manual file list |
|
||||
| Documents missing (spec quality) | Score as 0 for completeness, report to coordinator |
|
||||
| Cannot parse YAML frontmatter | Skip consistency check for that document |
|
||||
| Grep pattern errors | Skip specific check, continue with remaining |
|
||||
| CLI analysis timeout | Report partial results, note incomplete analysis |
|
||||
| Session folder not found | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
@@ -1,133 +1,68 @@
|
||||
---
|
||||
name: test
|
||||
description: Team tester - 自适应测试修复循环、渐进式测试、报告结果给coordinator
|
||||
argument-hint: ""
|
||||
allowed-tools: SendMessage(*), TaskUpdate(*), TaskList(*), TaskGet(*), TodoWrite(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*), Task(*)
|
||||
group: team
|
||||
---
|
||||
# Role: tester
|
||||
|
||||
# Team Test Command (/team:test)
|
||||
Adaptive test-fix cycle with progressive testing strategy. Detects test framework, applies multi-strategy fixes, and reports results to coordinator.
|
||||
|
||||
## Overview
|
||||
## Role Identity
|
||||
|
||||
Team tester role command. Operates as a teammate within an Agent Team, responsible for test execution with adaptive fix cycles and progressive testing. Reports results to the coordinator.
|
||||
- **Name**: `tester`
|
||||
- **Task Prefix**: `TEST-*`
|
||||
- **Responsibility**: Detect Framework → Run Tests → Fix Cycle → Report Results
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
**Core capabilities:**
|
||||
- Task discovery from shared team task list (TEST-* tasks)
|
||||
- Test framework auto-detection (jest/vitest/pytest/mocha)
|
||||
- Adaptive strategy engine: conservative → aggressive → surgical
|
||||
- Progressive testing: affected tests during iterations, full suite for final validation
|
||||
- Fix cycle with max iterations and quality gate (>= 95% pass rate)
|
||||
- Structured result reporting to coordinator
|
||||
## Message Types
|
||||
|
||||
## Role Definition
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `test_result` | tester → coordinator | Test cycle ends (pass or max iterations) | With pass rate, iteration count, remaining failures |
|
||||
| `impl_progress` | tester → coordinator | Fix cycle intermediate progress | Optional, for long fix cycles (iteration > 5) |
|
||||
| `fix_required` | tester → coordinator | Found issues beyond tester scope | Architecture/design problems needing executor |
|
||||
| `error` | tester → coordinator | Framework unavailable or crash | Command not found, timeout, environment issues |
|
||||
|
||||
**Name**: `tester`
|
||||
**Responsibility**: Run tests → Fix cycle → Report results
|
||||
**Communication**: SendMessage to coordinator only
|
||||
## Message Bus
|
||||
|
||||
## 消息总线
|
||||
|
||||
每次 SendMessage **前**,必须调用 `mcp__ccw-tools__team_msg` 记录消息:
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "<type>", summary: "<摘要>" })
|
||||
// Test result
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "test_result", summary: "TEST passed: 98% pass rate, 3 iterations", data: { passRate: 98, iterations: 3, total: 50, passed: 49 } })
|
||||
|
||||
// Progress update (long fix cycles)
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "impl_progress", summary: "Fix iteration 6: 85% pass rate" })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "error", summary: "vitest command not found, falling back to npm test" })
|
||||
```
|
||||
|
||||
### 支持的 Message Types
|
||||
### CLI Fallback
|
||||
|
||||
| Type | 方向 | 触发时机 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `test_result` | tester → coordinator | 测试循环结束(通过或达到最大迭代) | 附带 pass rate、迭代次数、剩余失败 |
|
||||
| `impl_progress` | tester → coordinator | 修复循环中间进度 | 可选,长时间修复时使用(如迭代>5) |
|
||||
| `fix_required` | tester → coordinator | 测试发现需要 executor 修复的问题 | 超出 tester 修复能力的架构/设计问题 |
|
||||
| `error` | tester → coordinator | 测试框架不可用或测试执行崩溃 | 命令未找到、超时、环境问题等 |
|
||||
|
||||
### 调用示例
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
// 测试通过
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "test_result", summary: "TEST-001通过: 98% pass rate, 3次迭代", data: { passRate: 98, iterations: 3, total: 42, passed: 41, failed: 1 } })
|
||||
|
||||
// 测试未达标
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "test_result", summary: "TEST-001未达标: 82% pass rate, 10次迭代已用完", data: { passRate: 82, iterations: 10, criticalFailures: 2 } })
|
||||
|
||||
// 需要 executor 修复
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "fix_required", summary: "数据库连接池配置导致集成测试全部失败, 需executor修复" })
|
||||
|
||||
// 错误上报
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "error", summary: "jest命令未找到, 请确认测试框架已安装" })
|
||||
Bash(`ccw team log --team "${teamName}" --from "tester" --to "coordinator" --type "test_result" --summary "TEST passed: 98% pass rate" --data '{"passRate":98,"iterations":3}' --json`)
|
||||
```
|
||||
|
||||
### CLI 回退
|
||||
|
||||
当 `mcp__ccw-tools__team_msg` MCP 不可用时,使用 `ccw team` CLI 作为等效回退:
|
||||
|
||||
```javascript
|
||||
// 回退: 将 MCP 调用替换为 Bash CLI(参数一一对应)
|
||||
Bash(`ccw team log --team "${teamName}" --from "tester" --to "coordinator" --type "test_result" --summary "TEST-001通过: 98% pass rate" --data '{"passRate":98,"iterations":3}' --json`)
|
||||
```
|
||||
|
||||
**参数映射**: `team_msg(params)` → `ccw team log --team <team> --from tester --to coordinator --type <type> --summary "<text>" [--data '<json>'] [--json]`
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Phase 1: Task Discovery
|
||||
├─ TaskList to find unblocked TEST-* tasks assigned to me
|
||||
├─ TaskGet to read full task details
|
||||
└─ TaskUpdate to mark in_progress
|
||||
|
||||
Phase 2: Test Framework Detection
|
||||
├─ Detect framework: jest/vitest/pytest/mocha
|
||||
├─ Identify test command from package.json/pyproject.toml
|
||||
└─ Locate affected test files based on changed files
|
||||
|
||||
Phase 3: Test Execution & Fix Cycle (max 10 iterations)
|
||||
├─ Strategy Engine:
|
||||
│ ├─ Iteration 1-2: Conservative (single targeted fix)
|
||||
│ ├─ Pass rate > 80% + similar failures: Aggressive (batch fix)
|
||||
│ └─ Regression detected (drop > 10%): Surgical (minimal + rollback)
|
||||
├─ Progressive Testing:
|
||||
│ ├─ Iterations: run affected tests only
|
||||
│ └─ Final: full test suite validation
|
||||
└─ Quality Gate: pass rate >= 95%
|
||||
|
||||
Phase 4: Result Analysis
|
||||
├─ Calculate final pass rate
|
||||
├─ Classify failure severity (critical/high/medium/low)
|
||||
└─ Generate test summary
|
||||
|
||||
Phase 5: Report to Coordinator
|
||||
├─ SendMessage with test results
|
||||
├─ >= 95%: mark TEST task completed
|
||||
└─ < 95% after max iterations: report needs intervention
|
||||
```
|
||||
|
||||
## Implementation
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
// Find my assigned TEST tasks
|
||||
const tasks = TaskList()
|
||||
const myTestTasks = tasks.filter(t =>
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('TEST-') &&
|
||||
t.owner === 'tester' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (myTestTasks.length === 0) return // idle
|
||||
if (myTasks.length === 0) return // idle
|
||||
|
||||
const task = TaskGet({ taskId: myTestTasks[0].id })
|
||||
const task = TaskGet({ taskId: myTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
```
|
||||
|
||||
### Phase 2: Test Framework Detection
|
||||
|
||||
```javascript
|
||||
// Detect test framework
|
||||
function detectTestFramework() {
|
||||
// Check package.json
|
||||
try {
|
||||
@@ -144,17 +79,15 @@ function detectTestFramework() {
|
||||
if (pyproject.includes('pytest')) return { framework: 'pytest', command: 'pytest' }
|
||||
} catch {}
|
||||
|
||||
// Fallback
|
||||
return { framework: 'unknown', command: 'npm test' }
|
||||
}
|
||||
|
||||
const testConfig = detectTestFramework()
|
||||
|
||||
// Locate affected test files
|
||||
// Locate affected test files from changed files
|
||||
function findAffectedTests(changedFiles) {
|
||||
const testFiles = []
|
||||
for (const file of changedFiles) {
|
||||
// Convention: src/foo.ts → tests/foo.test.ts or __tests__/foo.test.ts
|
||||
const testVariants = [
|
||||
file.replace(/\/src\//, '/tests/').replace(/\.(ts|js|tsx|jsx)$/, '.test.$1'),
|
||||
file.replace(/\/src\//, '/__tests__/').replace(/\.(ts|js|tsx|jsx)$/, '.test.$1'),
|
||||
@@ -169,7 +102,6 @@ function findAffectedTests(changedFiles) {
|
||||
return [...new Set(testFiles)]
|
||||
}
|
||||
|
||||
// Extract changed files from task description or git diff
|
||||
const changedFiles = Bash(`git diff --name-only HEAD~1 2>/dev/null || git diff --name-only --cached`).split('\n').filter(Boolean)
|
||||
const affectedTests = findAffectedTests(changedFiles)
|
||||
```
|
||||
@@ -202,20 +134,14 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
||||
previousPassRate = currentPassRate
|
||||
currentPassRate = results.passRate
|
||||
|
||||
// Record iteration
|
||||
iterationHistory.push({
|
||||
iteration,
|
||||
pass_rate: currentPassRate,
|
||||
strategy: strategy,
|
||||
failed_tests: results.failedTests,
|
||||
total: results.total,
|
||||
passed: results.passed
|
||||
iteration, pass_rate: currentPassRate, strategy,
|
||||
failed_tests: results.failedTests, total: results.total, passed: results.passed
|
||||
})
|
||||
|
||||
// Quality gate check
|
||||
if (currentPassRate >= PASS_RATE_TARGET) {
|
||||
if (!isFullSuite) {
|
||||
// Run full suite for final validation
|
||||
const fullOutput = Bash(`${testConfig.command} 2>&1 || true`, { timeout: 300000 })
|
||||
const fullResults = parseTestResults(fullOutput, testConfig.framework)
|
||||
currentPassRate = fullResults.passRate
|
||||
@@ -229,24 +155,25 @@ for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
||||
|
||||
// Apply fixes based on strategy
|
||||
applyFixes(results.failedTests, strategy, testOutput)
|
||||
|
||||
// Progress update for long cycles
|
||||
if (iteration > 5) {
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "tester", to: "coordinator", type: "impl_progress", summary: `修复迭代${iteration}: ${currentPassRate}% pass rate` })
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy Engine
|
||||
function selectStrategy(iteration, passRate, prevPassRate, history) {
|
||||
// Regression detection
|
||||
if (prevPassRate > 0 && passRate < prevPassRate - 10) return 'surgical'
|
||||
|
||||
// Iteration-based default
|
||||
if (iteration <= 2) return 'conservative'
|
||||
|
||||
// Pattern-based upgrade
|
||||
if (passRate > 80) {
|
||||
// Check if failures are similar (same test files, same error patterns)
|
||||
const recentFailures = history.slice(-2).flatMap(h => h.failed_tests)
|
||||
const uniqueFailures = [...new Set(recentFailures)]
|
||||
if (uniqueFailures.length <= recentFailures.length * 0.6) return 'aggressive'
|
||||
}
|
||||
|
||||
return 'conservative'
|
||||
}
|
||||
|
||||
@@ -254,26 +181,13 @@ function selectStrategy(iteration, passRate, prevPassRate, history) {
|
||||
function applyFixes(failedTests, strategy, testOutput) {
|
||||
switch (strategy) {
|
||||
case 'conservative':
|
||||
// Fix one failure at a time
|
||||
// Read failing test, understand error, apply targeted fix
|
||||
if (failedTests.length > 0) {
|
||||
const target = failedTests[0]
|
||||
// Analyze error message from testOutput
|
||||
// Read source file and test file
|
||||
// Apply minimal fix
|
||||
}
|
||||
// Fix one failure at a time - read failing test, understand error, apply targeted fix
|
||||
break
|
||||
|
||||
case 'aggressive':
|
||||
// Batch fix similar failures
|
||||
// Group by error pattern
|
||||
// Apply fixes to all related failures
|
||||
// Batch fix similar failures - group by error pattern, apply fixes to all related
|
||||
break
|
||||
|
||||
case 'surgical':
|
||||
// Minimal changes, consider rollback
|
||||
// Only fix the most critical failure
|
||||
// Verify no regression
|
||||
// Minimal changes, consider rollback - fix most critical failure only
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -281,40 +195,28 @@ function applyFixes(failedTests, strategy, testOutput) {
|
||||
// Test result parser
|
||||
function parseTestResults(output, framework) {
|
||||
let passed = 0, failed = 0, total = 0, failedTests = []
|
||||
|
||||
if (framework === 'jest' || framework === 'vitest') {
|
||||
const passMatch = output.match(/(\d+) passed/)
|
||||
const failMatch = output.match(/(\d+) failed/)
|
||||
passed = passMatch ? parseInt(passMatch[1]) : 0
|
||||
failed = failMatch ? parseInt(failMatch[1]) : 0
|
||||
total = passed + failed
|
||||
|
||||
// Extract failed test names
|
||||
const failPattern = /FAIL\s+(.+)/g
|
||||
let m
|
||||
while ((m = failPattern.exec(output)) !== null) failedTests.push(m[1].trim())
|
||||
} else if (framework === 'pytest') {
|
||||
const summaryMatch = output.match(/(\d+) passed.*?(\d+) failed/)
|
||||
if (summaryMatch) {
|
||||
passed = parseInt(summaryMatch[1])
|
||||
failed = parseInt(summaryMatch[2])
|
||||
}
|
||||
if (summaryMatch) { passed = parseInt(summaryMatch[1]); failed = parseInt(summaryMatch[2]) }
|
||||
total = passed + failed
|
||||
}
|
||||
|
||||
return {
|
||||
passed, failed, total,
|
||||
passRate: total > 0 ? Math.round((passed / total) * 100) : 100,
|
||||
failedTests
|
||||
}
|
||||
return { passed, failed, total, passRate: total > 0 ? Math.round((passed / total) * 100) : 100, failedTests }
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Result Analysis
|
||||
|
||||
```javascript
|
||||
// Classify failure severity
|
||||
function classifyFailures(failedTests, testOutput) {
|
||||
function classifyFailures(failedTests) {
|
||||
return failedTests.map(test => {
|
||||
const testLower = test.toLowerCase()
|
||||
let severity = 'low'
|
||||
@@ -326,11 +228,7 @@ function classifyFailures(failedTests, testOutput) {
|
||||
})
|
||||
}
|
||||
|
||||
const classifiedFailures = classifyFailures(
|
||||
iterationHistory[iterationHistory.length - 1]?.failed_tests || [],
|
||||
'' // last test output
|
||||
)
|
||||
|
||||
const classifiedFailures = classifyFailures(iterationHistory[iterationHistory.length - 1]?.failed_tests || [])
|
||||
const hasCriticalFailures = classifiedFailures.some(f => f.severity === 'critical')
|
||||
```
|
||||
|
||||
@@ -340,6 +238,14 @@ const hasCriticalFailures = classifiedFailures.some(f => f.severity === 'critica
|
||||
const finalIteration = iterationHistory[iterationHistory.length - 1]
|
||||
const success = currentPassRate >= PASS_RATE_TARGET
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "tester", to: "coordinator",
|
||||
type: "test_result",
|
||||
summary: `TEST${success ? '通过' : '未达标'}: ${currentPassRate}% pass rate, ${iterationHistory.length}次迭代`,
|
||||
data: { passRate: currentPassRate, iterations: iterationHistory.length, total: finalIteration?.total || 0, passed: finalIteration?.passed || 0 }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
@@ -369,34 +275,20 @@ if (success) {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Keep in_progress, coordinator decides next steps
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `Test pass rate ${currentPassRate}% is below ${PASS_RATE_TARGET}% after ${MAX_ITERATIONS} iterations. Need coordinator decision on next steps.`,
|
||||
summary: "Test target not met, need guidance"
|
||||
})
|
||||
}
|
||||
|
||||
// Check for next TEST task
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('TEST-') &&
|
||||
t.owner === 'tester' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (nextTasks.length > 0) {
|
||||
// Continue with next task
|
||||
}
|
||||
// Check for next TEST task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No TEST-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Test command not found | Detect framework, try alternatives (npm test, pytest, etc.) |
|
||||
| Test execution timeout | Reduce test scope, retry with affected tests only |
|
||||
| Regression detected (pass rate drops > 10%) | Switch to surgical strategy, consider rollback |
|
||||
| Stuck tests (same failure 3+ iterations) | Report to coordinator, suggest different approach |
|
||||
| Max iterations reached < 95% | Report failure details, let coordinator decide |
|
||||
| No test files found | Report to coordinator, suggest test generation needed |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
192
.claude/skills/team-lifecycle/roles/writer.md
Normal file
192
.claude/skills/team-lifecycle/roles/writer.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Role: writer
|
||||
|
||||
Product Brief, Requirements/PRD, Architecture, and Epics & Stories document generation. Maps to spec-generator Phases 2-5.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `writer`
|
||||
- **Task Prefix**: `DRAFT-*`
|
||||
- **Responsibility**: Load Context → Generate Document → Incorporate Feedback → Report
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `draft_ready` | writer → coordinator | Document writing complete | With document path and type |
|
||||
| `draft_revision` | writer → coordinator | Document revised and resubmitted | Describes changes made |
|
||||
| `impl_progress` | writer → coordinator | Long writing progress | Multi-document stage progress |
|
||||
| `error` | writer → coordinator | Unrecoverable error | Template missing, insufficient context, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Document ready
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "writer", to: "coordinator", type: "draft_ready", summary: "Product Brief complete", ref: `${sessionFolder}/product-brief.md` })
|
||||
|
||||
// Document revision
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "writer", to: "coordinator", type: "draft_revision", summary: "Requirements revised per discussion feedback" })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "writer", to: "coordinator", type: "error", summary: "Input artifact missing, cannot generate document" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "writer" --to "coordinator" --type "draft_ready" --summary "Brief complete" --ref "${sessionFolder}/product-brief.md" --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('DRAFT-') &&
|
||||
t.owner === 'writer' &&
|
||||
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: Context & Discussion Loading
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load session config
|
||||
let specConfig = null
|
||||
try { specConfig = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
|
||||
// Determine document type from task subject
|
||||
const docType = task.subject.includes('Product Brief') ? 'product-brief'
|
||||
: task.subject.includes('Requirements') || task.subject.includes('PRD') ? 'requirements'
|
||||
: task.subject.includes('Architecture') ? 'architecture'
|
||||
: task.subject.includes('Epics') ? 'epics'
|
||||
: 'unknown'
|
||||
|
||||
// Load discussion feedback (from preceding DISCUSS task)
|
||||
const discussionFiles = {
|
||||
'product-brief': 'discussions/discuss-001-scope.md',
|
||||
'requirements': 'discussions/discuss-002-brief.md',
|
||||
'architecture': 'discussions/discuss-003-requirements.md',
|
||||
'epics': 'discussions/discuss-004-architecture.md'
|
||||
}
|
||||
let discussionFeedback = null
|
||||
try { discussionFeedback = Read(`${sessionFolder}/${discussionFiles[docType]}`) } catch {}
|
||||
|
||||
// Load prior documents progressively
|
||||
const priorDocs = {}
|
||||
if (docType !== 'product-brief') {
|
||||
try { priorDocs.discoveryContext = Read(`${sessionFolder}/discovery-context.json`) } catch {}
|
||||
}
|
||||
if (['requirements', 'architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
}
|
||||
if (['architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
}
|
||||
if (docType === 'epics') {
|
||||
try { priorDocs.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Document Generation (type-specific)
|
||||
|
||||
Route to specific generation logic based on document type:
|
||||
|
||||
**DRAFT-001: Product Brief** — Multi-perspective analysis using 3 parallel CLI analyses (product/technical/user), then synthesize into product-brief.md with YAML frontmatter.
|
||||
|
||||
**DRAFT-002: Requirements/PRD** — Expand requirements from Product Brief via CLI. Generate REQ-NNN functional requirements + NFR-{type}-NNN non-functional requirements with MoSCoW prioritization. Output to requirements/ directory.
|
||||
|
||||
**DRAFT-003: Architecture** — Design system architecture from requirements via CLI. Generate architecture/_index.md + ADR-*.md files with tech stack, component diagrams (Mermaid), and data model.
|
||||
|
||||
**DRAFT-004: Epics & Stories** — Decompose requirements into EPIC-* with STORY-* user stories, cross-Epic dependency map, MVP scope definition, and execution order. Output to epics/ directory.
|
||||
|
||||
Each uses CLI tools (gemini/codex/claude) for multi-perspective analysis, with discussion feedback integration from the preceding DISCUSS round.
|
||||
|
||||
### Phase 4: Self-Validation
|
||||
|
||||
```javascript
|
||||
const validationChecks = {
|
||||
has_frontmatter: /^---\n[\s\S]+?\n---/.test(docContent),
|
||||
sections_complete: /* verify all required sections present */,
|
||||
cross_references: docContent.includes('session_id'),
|
||||
discussion_integrated: !discussionFeedback || docContent.includes('Discussion')
|
||||
}
|
||||
|
||||
const allValid = Object.values(validationChecks).every(v => v)
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const docTypeLabel = {
|
||||
'product-brief': 'Product Brief',
|
||||
'requirements': 'Requirements/PRD',
|
||||
'architecture': 'Architecture Document',
|
||||
'epics': 'Epics & Stories'
|
||||
}
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "writer", to: "coordinator",
|
||||
type: "draft_ready",
|
||||
summary: `${docTypeLabel[docType]} 完成: ${allValid ? '验证通过' : '部分验证失败'}`,
|
||||
ref: `${sessionFolder}/${outputPath}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 文档撰写结果
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**文档类型**: ${docTypeLabel[docType]}
|
||||
**验证状态**: ${allValid ? 'PASS' : 'PARTIAL'}
|
||||
|
||||
### 文档摘要
|
||||
${documentSummary}
|
||||
|
||||
### 讨论反馈整合
|
||||
${discussionFeedback ? '已整合前序讨论反馈' : '首次撰写'}
|
||||
|
||||
### 自验证结果
|
||||
${Object.entries(validationChecks).map(([k, v]) => '- ' + k + ': ' + (v ? 'PASS' : 'FAIL')).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
${sessionFolder}/${outputPath}
|
||||
|
||||
文档已就绪,可进入讨论轮次。`,
|
||||
summary: `${docTypeLabel[docType]} 就绪`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
// Check for next DRAFT task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No DRAFT-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Prior document not found | Notify coordinator, request prerequisite |
|
||||
| CLI analysis failure | Retry with fallback tool, then direct generation |
|
||||
| Template sections incomplete | Generate best-effort, note gaps in report |
|
||||
| Discussion feedback contradicts prior docs | Note conflict in document, flag for next discussion |
|
||||
| Session folder missing | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
78
.claude/skills/team-lifecycle/specs/team-config.json
Normal file
78
.claude/skills/team-lifecycle/specs/team-config.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"team_name": "team-lifecycle",
|
||||
"team_display_name": "Team Lifecycle",
|
||||
"description": "Unified team skill covering spec-to-dev-to-test full lifecycle",
|
||||
"version": "1.0.0",
|
||||
|
||||
"roles": {
|
||||
"coordinator": {
|
||||
"task_prefix": null,
|
||||
"responsibility": "Pipeline orchestration, requirement clarification, task chain creation, message dispatch",
|
||||
"message_types": ["plan_approved", "plan_revision", "task_unblocked", "fix_required", "error", "shutdown"]
|
||||
},
|
||||
"analyst": {
|
||||
"task_prefix": "RESEARCH",
|
||||
"responsibility": "Seed analysis, codebase exploration, multi-dimensional context gathering",
|
||||
"message_types": ["research_ready", "research_progress", "error"]
|
||||
},
|
||||
"writer": {
|
||||
"task_prefix": "DRAFT",
|
||||
"responsibility": "Product Brief / PRD / Architecture / Epics document generation",
|
||||
"message_types": ["draft_ready", "draft_revision", "impl_progress", "error"]
|
||||
},
|
||||
"discussant": {
|
||||
"task_prefix": "DISCUSS",
|
||||
"responsibility": "Multi-perspective critique, consensus building, conflict escalation",
|
||||
"message_types": ["discussion_ready", "discussion_blocked", "impl_progress", "error"]
|
||||
},
|
||||
"planner": {
|
||||
"task_prefix": "PLAN",
|
||||
"responsibility": "Multi-angle code exploration, structured implementation planning",
|
||||
"message_types": ["plan_ready", "plan_revision", "impl_progress", "error"]
|
||||
},
|
||||
"executor": {
|
||||
"task_prefix": "IMPL",
|
||||
"responsibility": "Code implementation following approved plans",
|
||||
"message_types": ["impl_complete", "impl_progress", "error"]
|
||||
},
|
||||
"tester": {
|
||||
"task_prefix": "TEST",
|
||||
"responsibility": "Adaptive test-fix cycles, progressive testing, quality gates",
|
||||
"message_types": ["test_result", "impl_progress", "fix_required", "error"]
|
||||
},
|
||||
"reviewer": {
|
||||
"task_prefix": "REVIEW",
|
||||
"additional_prefixes": ["QUALITY"],
|
||||
"responsibility": "Code review (REVIEW-*) + Spec quality validation (QUALITY-*)",
|
||||
"message_types": ["review_result", "quality_result", "fix_required", "error"]
|
||||
}
|
||||
},
|
||||
|
||||
"pipelines": {
|
||||
"spec-only": {
|
||||
"description": "Specification pipeline: research → discuss → draft → quality",
|
||||
"task_chain": [
|
||||
"RESEARCH-001",
|
||||
"DISCUSS-001", "DRAFT-001", "DISCUSS-002",
|
||||
"DRAFT-002", "DISCUSS-003", "DRAFT-003", "DISCUSS-004",
|
||||
"DRAFT-004", "DISCUSS-005", "QUALITY-001", "DISCUSS-006"
|
||||
]
|
||||
},
|
||||
"impl-only": {
|
||||
"description": "Implementation pipeline: plan → implement → test + review",
|
||||
"task_chain": ["PLAN-001", "IMPL-001", "TEST-001", "REVIEW-001"]
|
||||
},
|
||||
"full-lifecycle": {
|
||||
"description": "Full lifecycle: spec pipeline → implementation pipeline",
|
||||
"task_chain": "spec-only + impl-only (PLAN-001 blockedBy DISCUSS-006)"
|
||||
}
|
||||
},
|
||||
|
||||
"collaboration_patterns": ["CP-1", "CP-2", "CP-4", "CP-5", "CP-6", "CP-10"],
|
||||
|
||||
"session_dirs": {
|
||||
"spec": ".workflow/.spec-team/{topic-slug}-{YYYY-MM-DD}/",
|
||||
"impl": ".workflow/.team-plan/{task-slug}-{YYYY-MM-DD}/",
|
||||
"messages": ".workflow/.team-msg/{team-name}/"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user