mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
Remove obsolete documentation and configuration files for team lifecycle specifications
- Deleted document standards for spec-generator outputs. - Removed quality gates criteria and scoring dimensions. - Eliminated team configuration JSON file. - Cleared architecture document template for generating ADRs. - Purged epics and stories template for breakdown generation. - Erased product brief template for Phase 2 documentation. - Removed requirements PRD template for Phase 3 documentation.
This commit is contained in:
@@ -1,410 +0,0 @@
|
||||
---
|
||||
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 1.5: Resume Artifact Check (防止重复产出)
|
||||
// 当 session 从暂停恢复时,coordinator 已将 in_progress 任务重置为 pending。
|
||||
// Worker 在开始工作前,必须检查该任务的输出产物是否已存在。
|
||||
// 如果产物已存在且内容完整:
|
||||
// → 直接跳到 Phase 5 报告完成(避免覆盖上次成果)
|
||||
// 如果产物存在但不完整(如文件为空或缺少关键 section):
|
||||
// → 正常执行 Phase 2-4(基于已有产物继续,而非从头开始)
|
||||
// 如果产物不存在:
|
||||
// → 正常执行 Phase 2-4
|
||||
//
|
||||
// 每个 role 检查自己的输出路径:
|
||||
// analyst → sessionFolder/spec/discovery-context.json
|
||||
// writer → sessionFolder/spec/{product-brief.md | requirements/ | architecture/ | epics/}
|
||||
// discussant → sessionFolder/discussions/discuss-NNN-*.md
|
||||
// planner → sessionFolder/plan/plan.json
|
||||
// executor → git diff (已提交的代码变更)
|
||||
// tester → test pass rate
|
||||
// reviewer → sessionFolder/spec/readiness-report.md (quality) 或 review findings (code)
|
||||
|
||||
// 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
|
||||
```
|
||||
|
||||
## Unified Session Directory
|
||||
|
||||
All session artifacts are stored under a single session folder:
|
||||
|
||||
```
|
||||
.workflow/.team/TLS-{slug}-{YYYY-MM-DD}/
|
||||
├── team-session.json # Session state (status, progress, completed_tasks)
|
||||
├── spec/ # Spec artifacts (analyst, writer, reviewer output)
|
||||
│ ├── spec-config.json
|
||||
│ ├── discovery-context.json
|
||||
│ ├── product-brief.md
|
||||
│ ├── requirements/ # _index.md + REQ-*.md + NFR-*.md
|
||||
│ ├── architecture/ # _index.md + ADR-*.md
|
||||
│ ├── epics/ # _index.md + EPIC-*.md
|
||||
│ ├── readiness-report.md
|
||||
│ └── spec-summary.md
|
||||
├── discussions/ # Discussion records (discussant output)
|
||||
│ └── discuss-001..006.md
|
||||
└── plan/ # Plan artifacts (planner output)
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
Messages remain at `.workflow/.team-msg/{team-name}/` (unchanged).
|
||||
|
||||
## Session Resume
|
||||
|
||||
Coordinator supports `--resume` / `--continue` flags to resume interrupted sessions:
|
||||
|
||||
1. Scans `.workflow/.team/TLS-*/team-session.json` for `status: "active"` or `"paused"`
|
||||
2. Multiple matches → `AskUserQuestion` for user selection
|
||||
3. **Audit TaskList** — 获取当前所有任务的真实状态
|
||||
4. **Reconcile** — 双向同步 session.completed_tasks ↔ TaskList 状态:
|
||||
- session 已完成但 TaskList 未标记 → 修正 TaskList 为 completed
|
||||
- TaskList 已完成但 session 未记录 → 补录到 session
|
||||
- in_progress 状态(暂停中断)→ 重置为 pending
|
||||
5. Determines remaining pipeline from reconciled state
|
||||
6. Rebuilds team (`TeamCreate` + worker spawns for needed roles only)
|
||||
7. Creates missing tasks with correct `blockedBy` dependency chain (uses `TASK_METADATA` lookup)
|
||||
8. Verifies dependency chain integrity for existing tasks
|
||||
9. Updates session file with reconciled state + current_phase
|
||||
10. **Kick** — 向首个可执行任务的 worker 发送 `task_unblocked` 消息,打破 resume 死锁
|
||||
11. Jumps to Phase 4 coordination loop
|
||||
|
||||
## Coordinator Spawn Template
|
||||
|
||||
When coordinator creates teammates:
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Analyst (spec-only / full)
|
||||
Task({
|
||||
subagent_type: "general-purpose",
|
||||
description: `Spawn analyst worker`,
|
||||
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",
|
||||
description: `Spawn writer worker`,
|
||||
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",
|
||||
description: `Spawn discussant worker`,
|
||||
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",
|
||||
description: `Spawn planner worker`,
|
||||
team_name: teamName,
|
||||
name: "planner",
|
||||
prompt: `你是 team "${teamName}" 的 PLANNER。
|
||||
当你收到 PLAN-* 任务时,调用 Skill(skill="team-lifecycle", args="--role=planner") 执行。
|
||||
当前需求: ${taskDescription}
|
||||
约束: ${constraints}
|
||||
Session: ${sessionFolder}
|
||||
|
||||
## 消息总线(必须)
|
||||
每次 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",
|
||||
description: `Spawn executor worker`,
|
||||
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",
|
||||
description: `Spawn tester worker`,
|
||||
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",
|
||||
description: `Spawn reviewer worker`,
|
||||
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 → 检查下一个任务`
|
||||
})
|
||||
```
|
||||
|
||||
## Shared Spec Resources
|
||||
|
||||
Writer 和 Reviewer 角色在 spec 模式下使用本 skill 内置的标准和模板(从 spec-generator 复制,独立维护):
|
||||
|
||||
| Resource | Path | Usage |
|
||||
|----------|------|-------|
|
||||
| Document Standards | `specs/document-standards.md` | YAML frontmatter、命名规范、内容结构 |
|
||||
| Quality Gates | `specs/quality-gates.md` | Per-phase 质量门禁、评分标尺 |
|
||||
| Product Brief Template | `templates/product-brief.md` | DRAFT-001 文档生成 |
|
||||
| Requirements Template | `templates/requirements-prd.md` | DRAFT-002 文档生成 |
|
||||
| Architecture Template | `templates/architecture-doc.md` | DRAFT-003 文档生成 |
|
||||
| Epics Template | `templates/epics-template.md` | DRAFT-004 文档生成 |
|
||||
|
||||
> Writer 在执行每个 DRAFT-* 任务前 **必须先 Read** 对应的 template 文件和 document-standards.md。
|
||||
> 从 `roles/` 子目录引用时路径为 `../specs/` 和 `../templates/`。
|
||||
|
||||
## 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 |
|
||||
@@ -1,215 +0,0 @@
|
||||
# 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}/spec/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/.team/default'
|
||||
|
||||
// Parse topic from task description
|
||||
const topicLines = task.description.split('\n').filter(l => !l.startsWith('Session:') && !l.startsWith('输出:') && l.trim())
|
||||
const rawTopic = topicLines[0] || task.subject.replace('RESEARCH-001: ', '')
|
||||
|
||||
// 支持文件引用输入(与 spec-generator Phase 1 一致)
|
||||
const topic = (rawTopic.startsWith('@') || rawTopic.endsWith('.md') || rawTopic.endsWith('.txt'))
|
||||
? Read(rawTopic.replace(/^@/, ''))
|
||||
: rawTopic
|
||||
|
||||
// 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",
|
||||
depth: task.description.match(/讨论深度:\s*(.+)/)?.[1] || "standard",
|
||||
focus_areas: seedAnalysis.exploration_dimensions || [],
|
||||
mode: "interactive", // team 模式始终交互
|
||||
phases_completed: ["discovery"],
|
||||
created_at: new Date().toISOString(),
|
||||
session_folder: sessionFolder,
|
||||
discussion_depth: task.description.match(/讨论深度:\s*(.+)/)?.[1] || "standard"
|
||||
}
|
||||
Write(`${sessionFolder}/spec/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}/spec/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/spec-config.json
|
||||
- Context: ${sessionFolder}/spec/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 |
|
||||
@@ -1,925 +0,0 @@
|
||||
# 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 0: Session Resume Check
|
||||
|
||||
Before any new session setup, check if resuming an existing session:
|
||||
|
||||
```javascript
|
||||
const args = "$ARGUMENTS"
|
||||
const isResume = /--resume|--continue/.test(args)
|
||||
|
||||
if (isResume) {
|
||||
// Scan for active/paused sessions
|
||||
const sessionDirs = Glob({ pattern: '.workflow/.team/TLS-*/team-session.json' })
|
||||
const resumable = sessionDirs.map(f => {
|
||||
try {
|
||||
const session = JSON.parse(Read(f))
|
||||
if (session.status === 'active' || session.status === 'paused') return session
|
||||
} catch {}
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
|
||||
if (resumable.length === 0) {
|
||||
// No resumable sessions → fall through to Phase 1
|
||||
} else if (resumable.length === 1) {
|
||||
var resumedSession = resumable[0]
|
||||
} else {
|
||||
// Multiple matches → user selects
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "检测到多个可恢复的会话,请选择:",
|
||||
header: "Resume",
|
||||
multiSelect: false,
|
||||
options: resumable.slice(0, 4).map(s => ({
|
||||
label: s.session_id,
|
||||
description: `${s.topic} (${s.current_phase}, ${s.status})`
|
||||
}))
|
||||
}]
|
||||
})
|
||||
var resumedSession = resumable.find(s => s.session_id === userChoice)
|
||||
}
|
||||
|
||||
if (resumedSession) {
|
||||
// Restore session state
|
||||
const teamName = resumedSession.team_name
|
||||
const mode = resumedSession.mode
|
||||
const sessionFolder = `.workflow/.team/${resumedSession.session_id}`
|
||||
const taskDescription = resumedSession.topic
|
||||
const executionMethod = resumedSession.user_preferences?.execution_method || 'Auto'
|
||||
const codeReviewTool = resumedSession.user_preferences?.code_review || 'Skip'
|
||||
|
||||
// ============================================================
|
||||
// Pipeline Constants
|
||||
// ============================================================
|
||||
const SPEC_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'
|
||||
]
|
||||
const IMPL_CHAIN = ['PLAN-001', 'IMPL-001', 'TEST-001', 'REVIEW-001']
|
||||
|
||||
// Task metadata: prefix → { subject, owner, description template, activeForm }
|
||||
const TASK_METADATA = {
|
||||
'RESEARCH-001': { owner: 'analyst', subject: 'RESEARCH-001: 主题发现与上下文研究', activeForm: '研究中',
|
||||
desc: () => `${taskDescription}\n\nSession: ${sessionFolder}\n输出: ${sessionFolder}/spec/spec-config.json + spec/discovery-context.json` },
|
||||
'DISCUSS-001': { owner: 'discussant', subject: 'DISCUSS-001: 研究结果讨论 - 范围确认与方向调整', activeForm: '讨论范围中',
|
||||
desc: () => `讨论 RESEARCH-001 的发现结果\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/discovery-context.json\n输出: ${sessionFolder}/discussions/discuss-001-scope.md` },
|
||||
'DRAFT-001': { owner: 'writer', subject: 'DRAFT-001: 撰写 Product Brief', activeForm: '撰写 Brief 中',
|
||||
desc: () => `基于研究和讨论共识撰写产品简报\n\nSession: ${sessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${sessionFolder}/spec/product-brief.md` },
|
||||
'DISCUSS-002': { owner: 'discussant', subject: 'DISCUSS-002: Product Brief 多视角评审', activeForm: '评审 Brief 中',
|
||||
desc: () => `评审 Product Brief 文档\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/product-brief.md\n输出: ${sessionFolder}/discussions/discuss-002-brief.md` },
|
||||
'DRAFT-002': { owner: 'writer', subject: 'DRAFT-002: 撰写 Requirements/PRD', activeForm: '撰写 PRD 中',
|
||||
desc: () => `基于 Brief 和讨论反馈撰写需求文档\n\nSession: ${sessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${sessionFolder}/spec/requirements/` },
|
||||
'DISCUSS-003': { owner: 'discussant', subject: 'DISCUSS-003: 需求完整性与优先级讨论', activeForm: '讨论需求中',
|
||||
desc: () => `讨论 PRD 需求完整性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/requirements/_index.md\n输出: ${sessionFolder}/discussions/discuss-003-requirements.md` },
|
||||
'DRAFT-003': { owner: 'writer', subject: 'DRAFT-003: 撰写 Architecture Document', activeForm: '撰写架构中',
|
||||
desc: () => `基于需求和讨论反馈撰写架构文档\n\nSession: ${sessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${sessionFolder}/spec/architecture/` },
|
||||
'DISCUSS-004': { owner: 'discussant', subject: 'DISCUSS-004: 架构决策与技术可行性讨论', activeForm: '讨论架构中',
|
||||
desc: () => `讨论架构设计合理性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/architecture/_index.md\n输出: ${sessionFolder}/discussions/discuss-004-architecture.md` },
|
||||
'DRAFT-004': { owner: 'writer', subject: 'DRAFT-004: 撰写 Epics & Stories', activeForm: '撰写 Epics 中',
|
||||
desc: () => `基于架构和讨论反馈撰写史诗和用户故事\n\nSession: ${sessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${sessionFolder}/spec/epics/` },
|
||||
'DISCUSS-005': { owner: 'discussant', subject: 'DISCUSS-005: 执行计划与MVP范围讨论', activeForm: '讨论执行计划中',
|
||||
desc: () => `讨论执行计划就绪性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/epics/_index.md\n输出: ${sessionFolder}/discussions/discuss-005-epics.md` },
|
||||
'QUALITY-001': { owner: 'reviewer', subject: 'QUALITY-001: 规格就绪度检查', activeForm: '质量检查中',
|
||||
desc: () => `全文档交叉验证和质量评分\n\nSession: ${sessionFolder}\n输入: 全部文档\n输出: ${sessionFolder}/spec/readiness-report.md + spec/spec-summary.md` },
|
||||
'DISCUSS-006': { owner: 'discussant', subject: 'DISCUSS-006: 最终签收与交付确认', activeForm: '最终签收讨论中',
|
||||
desc: () => `最终讨论和签收\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/readiness-report.md\n输出: ${sessionFolder}/discussions/discuss-006-final.md` },
|
||||
'PLAN-001': { owner: 'planner', subject: 'PLAN-001: 探索和规划实现', activeForm: '规划中',
|
||||
desc: () => `${taskDescription}\n\nSession: ${sessionFolder}\n写入: ${sessionFolder}/plan/` },
|
||||
'IMPL-001': { owner: 'executor', subject: 'IMPL-001: 实现已批准的计划', activeForm: '实现中',
|
||||
desc: () => `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json\nexecution_method: ${executionMethod}\ncode_review: ${codeReviewTool}` },
|
||||
'TEST-001': { owner: 'tester', subject: 'TEST-001: 测试修复循环', activeForm: '测试中',
|
||||
desc: () => taskDescription },
|
||||
'REVIEW-001': { owner: 'reviewer', subject: 'REVIEW-001: 代码审查与需求验证', activeForm: '审查中',
|
||||
desc: () => `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json` }
|
||||
}
|
||||
|
||||
// Pipeline dependency: prefix → predecessor prefix (special: TEST-001 & REVIEW-001 both depend on IMPL-001)
|
||||
function getPredecessor(prefix, pipeline) {
|
||||
if (prefix === 'TEST-001' || prefix === 'REVIEW-001') return 'IMPL-001'
|
||||
const idx = pipeline.indexOf(prefix)
|
||||
return idx > 0 ? pipeline[idx - 1] : null
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Step 1: Audit TaskList — 审计当前任务清单状态
|
||||
// ============================================================
|
||||
const allTasks = TaskList()
|
||||
const pipeline = mode === 'spec-only' ? SPEC_CHAIN
|
||||
: mode === 'impl-only' ? IMPL_CHAIN
|
||||
: [...SPEC_CHAIN, ...IMPL_CHAIN]
|
||||
const sessionCompleted = new Set(resumedSession.completed_tasks || [])
|
||||
|
||||
// Build prefix → task mapping from existing TaskList
|
||||
const existingByPrefix = {}
|
||||
allTasks.forEach(t => {
|
||||
const prefixMatch = t.subject.match(/^([A-Z]+-\d+)/)
|
||||
if (prefixMatch) existingByPrefix[prefixMatch[1]] = t
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// Step 2: Reconcile — 同步 session 与 TaskList 状态
|
||||
// ============================================================
|
||||
const reconciledCompleted = new Set(sessionCompleted)
|
||||
const statusFixes = []
|
||||
|
||||
for (const prefix of pipeline) {
|
||||
const existing = existingByPrefix[prefix]
|
||||
if (!existing) continue
|
||||
|
||||
// Case A: session 记录已完成,但 TaskList 状态不是 completed → 修正 TaskList
|
||||
if (sessionCompleted.has(prefix) && existing.status !== 'completed') {
|
||||
TaskUpdate({ taskId: existing.id, status: 'completed' })
|
||||
statusFixes.push(`${prefix}: ${existing.status} → completed (sync from session)`)
|
||||
}
|
||||
|
||||
// Case B: TaskList 已 completed,但 session 未记录 → 补录 session
|
||||
if (existing.status === 'completed' && !sessionCompleted.has(prefix)) {
|
||||
reconciledCompleted.add(prefix)
|
||||
statusFixes.push(`${prefix}: completed (sync to session)`)
|
||||
}
|
||||
|
||||
// Case C: TaskList 是 in_progress(暂停时可能中断)→ 重置为 pending
|
||||
if (existing.status === 'in_progress' && !sessionCompleted.has(prefix)) {
|
||||
TaskUpdate({ taskId: existing.id, status: 'pending' })
|
||||
statusFixes.push(`${prefix}: in_progress → pending (reset for retry)`)
|
||||
}
|
||||
}
|
||||
|
||||
// Update session with reconciled completed_tasks
|
||||
resumedSession.completed_tasks = [...reconciledCompleted]
|
||||
|
||||
// ============================================================
|
||||
// Step 3: Determine remaining pipeline — 确定剩余任务顺序
|
||||
// ============================================================
|
||||
const remainingPipeline = pipeline.filter(p => !reconciledCompleted.has(p))
|
||||
|
||||
// ============================================================
|
||||
// Step 4: Rebuild team + Spawn workers — 重建团队
|
||||
// ============================================================
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Determine which worker roles are needed based on remaining tasks
|
||||
const neededRoles = new Set()
|
||||
remainingPipeline.forEach(prefix => {
|
||||
const meta = TASK_METADATA[prefix]
|
||||
if (meta) neededRoles.add(meta.owner)
|
||||
})
|
||||
|
||||
// Spawn only needed workers using Phase 4 Stop-Wait pattern (see SKILL.md Coordinator Spawn Template)
|
||||
// Workers are spawned per-stage via Task(run_in_background: false) in Phase 4 coordination loop.
|
||||
// neededRoles is used to determine which workers will be spawned on-demand.
|
||||
neededRoles.forEach(role => {
|
||||
// → Worker prompt template in SKILL.md (spawned per-stage in Phase 4, not pre-spawned here)
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// Step 5: Create missing tasks with correct dependencies
|
||||
// ============================================================
|
||||
// In a new conversation, TaskList is EMPTY — all remaining tasks must be created.
|
||||
// In a same-conversation resume, some tasks may already exist.
|
||||
const missingPrefixes = remainingPipeline.filter(p => !existingByPrefix[p])
|
||||
|
||||
for (const prefix of missingPrefixes) {
|
||||
const meta = TASK_METADATA[prefix]
|
||||
if (!meta) continue
|
||||
|
||||
// Create task
|
||||
const newTask = TaskCreate({
|
||||
subject: meta.subject,
|
||||
description: meta.desc(),
|
||||
activeForm: meta.activeForm
|
||||
})
|
||||
TaskUpdate({ taskId: newTask.id, owner: meta.owner })
|
||||
|
||||
// Register in existingByPrefix for dependency wiring
|
||||
existingByPrefix[prefix] = { id: newTask.id, status: 'pending', blockedBy: [] }
|
||||
|
||||
// Wire dependency: find predecessor
|
||||
const predPrefix = getPredecessor(prefix, pipeline)
|
||||
if (predPrefix && !reconciledCompleted.has(predPrefix)) {
|
||||
const predTask = existingByPrefix[predPrefix]
|
||||
if (predTask) {
|
||||
TaskUpdate({ taskId: newTask.id, addBlockedBy: [predTask.id] })
|
||||
}
|
||||
}
|
||||
|
||||
statusFixes.push(`${prefix}: created (missing in TaskList)`)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Step 6: Verify dependency chain integrity for existing tasks
|
||||
// ============================================================
|
||||
for (const prefix of remainingPipeline) {
|
||||
// Skip tasks we just created (already wired)
|
||||
if (missingPrefixes.includes(prefix)) continue
|
||||
const task = existingByPrefix[prefix]
|
||||
if (!task || task.status === 'completed') continue
|
||||
|
||||
const predPrefix = getPredecessor(prefix, pipeline)
|
||||
if (!predPrefix || reconciledCompleted.has(predPrefix)) continue
|
||||
|
||||
const predTask = existingByPrefix[predPrefix]
|
||||
if (predTask && task.blockedBy && !task.blockedBy.includes(predTask.id)) {
|
||||
TaskUpdate({ taskId: task.id, addBlockedBy: [predTask.id] })
|
||||
statusFixes.push(`${prefix}: added missing blockedBy → ${predPrefix}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Step 7: Update session file — 写入恢复状态
|
||||
// ============================================================
|
||||
resumedSession.status = 'active'
|
||||
resumedSession.resumed_at = new Date().toISOString()
|
||||
resumedSession.updated_at = new Date().toISOString()
|
||||
if (remainingPipeline.length > 0) {
|
||||
const firstRemaining = remainingPipeline[0]
|
||||
if (/^(RESEARCH|DISCUSS|DRAFT|QUALITY)/.test(firstRemaining)) {
|
||||
resumedSession.current_phase = 'spec'
|
||||
} else if (firstRemaining.startsWith('PLAN')) {
|
||||
resumedSession.current_phase = 'plan'
|
||||
} else {
|
||||
resumedSession.current_phase = 'impl'
|
||||
}
|
||||
}
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(resumedSession, null, 2))
|
||||
|
||||
// ============================================================
|
||||
// Step 8: Report reconciliation — 输出恢复摘要
|
||||
// ============================================================
|
||||
// Output to user:
|
||||
// - Session: {session_id} resumed
|
||||
// - Completed: {reconciledCompleted.size}/{pipeline.length} tasks
|
||||
// - Remaining: {remainingPipeline.join(' → ')}
|
||||
// - Status fixes: {statusFixes.length} corrections applied
|
||||
// - Next task: {remainingPipeline[0]}
|
||||
// - Workers spawned: {[...neededRoles].join(', ')}
|
||||
|
||||
// ============================================================
|
||||
// Step 9: Kick — 通知首个可执行任务的 worker 启动
|
||||
// ============================================================
|
||||
// 解决 resume 后的死锁:coordinator 等 worker 消息 ↔ worker 等任务
|
||||
// 找到第一个 pending + blockedBy 为空的任务,向其 owner 发送 task_unblocked
|
||||
const firstActionable = remainingPipeline.find(prefix => {
|
||||
const task = existingByPrefix[prefix]
|
||||
return task && task.status === 'pending' && (!task.blockedBy || task.blockedBy.length === 0)
|
||||
})
|
||||
|
||||
if (firstActionable) {
|
||||
const meta = TASK_METADATA[firstActionable]
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "coordinator", to: meta.owner,
|
||||
type: "task_unblocked",
|
||||
summary: `Resume: ${firstActionable} is ready for execution`
|
||||
})
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: meta.owner,
|
||||
content: `Session 已恢复。你的任务 ${firstActionable} 已就绪,请立即执行 TaskList 检查并开始工作。`,
|
||||
summary: `Resume kick: ${firstActionable}`
|
||||
})
|
||||
}
|
||||
|
||||
// → Skip to Phase 4 coordination loop
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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+/, '').replace(/--resume|--continue/, '').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.
|
||||
|
||||
#### Execution Method Selection (impl/full-lifecycle modes)
|
||||
|
||||
When mode includes implementation, select execution backend before team creation:
|
||||
|
||||
```javascript
|
||||
if (mode === 'impl-only' || mode === 'full-lifecycle') {
|
||||
const execSelection = AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "选择代码执行方式:",
|
||||
header: "Execution",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Agent", description: "code-developer agent(同步,适合简单任务)" },
|
||||
{ label: "Codex", description: "Codex CLI(后台,适合复杂任务)" },
|
||||
{ label: "Gemini", description: "Gemini CLI(后台,适合分析类任务)" },
|
||||
{ label: "Auto", description: "根据任务复杂度自动选择(默认)" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "实现后是否进行代码审查?",
|
||||
header: "Code Review",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Skip", description: "不审查(Reviewer 角色独立负责)" },
|
||||
{ label: "Gemini Review", description: "Gemini CLI 审查" },
|
||||
{ label: "Codex Review", description: "Git-aware review(--uncommitted)" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
var executionMethod = execSelection.Execution || 'Auto'
|
||||
var codeReviewTool = execSelection['Code Review'] || 'Skip'
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Create Team + Initialize Session
|
||||
|
||||
```javascript
|
||||
TeamCreate({ team_name: teamName })
|
||||
|
||||
// Unified 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 sessionId = `TLS-${topicSlug}-${dateStr}`
|
||||
const sessionFolder = `.workflow/.team/${sessionId}`
|
||||
|
||||
// Create unified directory structure
|
||||
if (mode === 'spec-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p "${sessionFolder}/spec" "${sessionFolder}/discussions"`)
|
||||
}
|
||||
if (mode === 'impl-only' || mode === 'full-lifecycle') {
|
||||
Bash(`mkdir -p "${sessionFolder}/plan"`)
|
||||
}
|
||||
|
||||
// Create team-session.json
|
||||
const teamSession = {
|
||||
session_id: sessionId,
|
||||
team_name: teamName,
|
||||
topic: taskDescription,
|
||||
mode: mode,
|
||||
status: "active",
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
paused_at: null,
|
||||
resumed_at: null,
|
||||
completed_at: null,
|
||||
current_phase: mode === 'impl-only' ? 'plan' : 'spec',
|
||||
completed_tasks: [],
|
||||
pipeline_progress: {
|
||||
spec: mode !== 'impl-only' ? { total: 12, completed: 0 } : null,
|
||||
impl: mode !== 'spec-only' ? { total: 4, completed: 0 } : null
|
||||
},
|
||||
user_preferences: { scope: scope || '', focus: focus || '', discussion_depth: discussionDepth || '' },
|
||||
messages_team: teamName
|
||||
}
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(teamSession, null, 2))
|
||||
```
|
||||
|
||||
**Workers are NOT pre-spawned here.** Workers are spawned per-stage in Phase 4 via Stop-Wait `Task(run_in_background: false)`. See SKILL.md Coordinator Spawn Template for worker prompt templates.
|
||||
|
||||
Worker roles by mode (spawned on-demand):
|
||||
|
||||
| Mode | Worker Roles |
|
||||
|------|-----------------|
|
||||
| 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: ${sessionFolder}\n输出: ${sessionFolder}/spec/spec-config.json + spec/discovery-context.json`, activeForm: "研究中" })
|
||||
TaskUpdate({ taskId: researchId, owner: "analyst" })
|
||||
|
||||
// DISCUSS-001: 范围讨论 (blockedBy RESEARCH-001)
|
||||
TaskCreate({ subject: "DISCUSS-001: 研究结果讨论 - 范围确认与方向调整", description: `讨论 RESEARCH-001 的发现结果\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/discovery-context.json\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: discovery-context.json + discuss-001-scope.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: ${sessionFolder}/spec/product-brief.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: product-brief.md + discuss-002-brief.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: ${sessionFolder}/spec/requirements/_index.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: requirements/ + discuss-003-requirements.md\n输出: ${sessionFolder}/architecture/\n\n包含: 架构风格 + 组件图 + 技术选型 + ADR-* + 数据模型`, activeForm: "撰写架构中" })
|
||||
TaskUpdate({ taskId: draft3Id, owner: "writer", addBlockedBy: [discuss3Id] })
|
||||
|
||||
// DISCUSS-004: 技术可行性 (blockedBy DRAFT-003)
|
||||
TaskCreate({ subject: "DISCUSS-004: 架构决策与技术可行性讨论", description: `讨论架构设计合理性\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/architecture/_index.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: architecture/ + discuss-004-architecture.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: ${sessionFolder}/spec/epics/_index.md\n输出: ${sessionFolder}/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: ${sessionFolder}\n输入: 全部文档\n输出: ${sessionFolder}/spec/readiness-report.md + spec/spec-summary.md\n\n评分维度: 完整性(20%) + 一致性(20%) + 可追溯性(20%) + 深度(20%) + 需求覆盖率(20%)`, activeForm: "质量检查中" })
|
||||
TaskUpdate({ taskId: qualityId, owner: "reviewer", addBlockedBy: [discuss5Id] })
|
||||
|
||||
// DISCUSS-006: 最终签收 (blockedBy QUALITY-001)
|
||||
TaskCreate({ subject: "DISCUSS-006: 最终签收与交付确认", description: `最终讨论和签收\n\nSession: ${sessionFolder}\n输入: ${sessionFolder}/spec/readiness-report.md\n输出: ${sessionFolder}/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\nSession: ${sessionFolder}\n写入: ${sessionFolder}/plan/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner" })
|
||||
|
||||
// IMPL-001 (blockedBy PLAN-001)
|
||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json\nexecution_method: ${executionMethod || 'Auto'}\ncode_review: ${codeReviewTool || 'Skip'}`, 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\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/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\nSession: ${sessionFolder}\n写入: ${sessionFolder}/plan/`, activeForm: "规划中" })
|
||||
TaskUpdate({ taskId: planId, owner: "planner", addBlockedBy: [discuss6Id] })
|
||||
// [Rest of impl-only tasks as above]
|
||||
```
|
||||
|
||||
### Phase 4: Coordination Loop
|
||||
|
||||
> **设计原则(Stop-Wait)**: 模型执行没有时间概念,禁止任何形式的轮询等待。
|
||||
> - ❌ 禁止: `while` 循环 + `sleep` + 检查状态
|
||||
> - ✅ 采用: 同步 `Task(run_in_background: false)` 调用,Worker 返回 = 阶段完成信号
|
||||
>
|
||||
> 按 Phase 3 创建的任务链顺序,逐阶段 spawn worker 同步执行。
|
||||
> Worker prompt 使用 SKILL.md Coordinator Spawn Template。
|
||||
|
||||
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.
|
||||
|
||||
#### Research Confirmation Checkpoint
|
||||
|
||||
When receiving `research_ready` from analyst, confirm extracted requirements with user before unblocking:
|
||||
|
||||
```javascript
|
||||
if (msgType === 'research_ready') {
|
||||
const discoveryContext = JSON.parse(Read(`${sessionFolder}/spec/discovery-context.json`))
|
||||
const dimensions = discoveryContext.seed_analysis?.exploration_dimensions || []
|
||||
const constraints = discoveryContext.seed_analysis?.constraints || []
|
||||
const problemStatement = discoveryContext.seed_analysis?.problem_statement || ''
|
||||
|
||||
// Present extracted requirements for user confirmation
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: `研究阶段提取到以下需求,请确认是否完整:\n\n**问题定义**: ${problemStatement}\n**探索维度**: ${dimensions.join('、')}\n**约束条件**: ${constraints.join('、')}\n\n是否有遗漏?`,
|
||||
header: "需求确认",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "确认完整", description: "提取的需求已覆盖所有关键点,继续推进" },
|
||||
{ label: "需要补充", description: "有遗漏的需求,我来补充" },
|
||||
{ label: "需要重新研究", description: "提取方向有偏差,重新执行研究" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
if (userChoice === '需要补充') {
|
||||
// User provides additional requirements via free text
|
||||
// Merge into discovery-context.json, then unblock DISCUSS-001
|
||||
discoveryContext.seed_analysis.user_supplements = userInput
|
||||
Write(`${sessionFolder}/spec/discovery-context.json`, JSON.stringify(discoveryContext, null, 2))
|
||||
} else if (userChoice === '需要重新研究') {
|
||||
// Reset RESEARCH-001 to pending, notify analyst
|
||||
TaskUpdate({ taskId: researchId, status: 'pending' })
|
||||
team_msg({ type: 'fix_required', summary: 'User requests re-research with revised scope' })
|
||||
return // Do not unblock DISCUSS-001
|
||||
}
|
||||
// '确认完整' → proceed normally: TaskUpdate RESEARCH completed
|
||||
}
|
||||
```
|
||||
|
||||
#### 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,新建任务链)===
|
||||
|
||||
// === 交付执行 → Handoff 逻辑 ===
|
||||
if (userChoice === '交付执行') {
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "选择交付方式:",
|
||||
header: "Handoff",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "lite-plan", description: "逐 Epic 轻量执行" },
|
||||
{ label: "full-plan", description: "完整规划(创建 WFS session + .brainstorming/ 桥接)" },
|
||||
{ label: "req-plan", description: "需求级路线图规划" },
|
||||
{ label: "create-issues", description: "每个 Epic 创建 issue" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
// 读取 spec 文档
|
||||
const specConfig = JSON.parse(Read(`${sessionFolder}/spec/spec-config.json`))
|
||||
const specSummary = Read(`${sessionFolder}/spec/spec-summary.md`)
|
||||
const productBrief = Read(`${sessionFolder}/spec/product-brief.md`)
|
||||
const requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`)
|
||||
const architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`)
|
||||
const epicsIndex = Read(`${sessionFolder}/spec/epics/_index.md`)
|
||||
const epicFiles = Glob(`${sessionFolder}/spec/epics/EPIC-*.md`)
|
||||
|
||||
if (handoffChoice === 'lite-plan') {
|
||||
// 读取首个 MVP Epic → 调用 lite-plan
|
||||
const firstMvpFile = epicFiles.find(f => {
|
||||
const content = Read(f)
|
||||
return content.includes('mvp: true')
|
||||
})
|
||||
const epicContent = Read(firstMvpFile)
|
||||
const title = epicContent.match(/^#\s+(.+)/m)?.[1] || ''
|
||||
const description = epicContent.match(/## Description\n([\s\S]*?)(?=\n## )/)?.[1]?.trim() || ''
|
||||
Skill({ skill: "workflow:lite-plan", args: `"${title}: ${description}"` })
|
||||
}
|
||||
|
||||
if (handoffChoice === 'full-plan' || handoffChoice === 'req-plan') {
|
||||
// === 桥接: 构建 .brainstorming/ 兼容结构 ===
|
||||
// 从 spec-generator Phase 6 Step 6 适配
|
||||
|
||||
// Step A: 构建结构化描述
|
||||
const structuredDesc = `GOAL: ${specConfig.seed_analysis?.problem_statement || specConfig.topic}
|
||||
SCOPE: ${specConfig.complexity} complexity
|
||||
CONTEXT: Generated from spec team session ${specConfig.session_id}. Source: ${sessionFolder}/`
|
||||
|
||||
// Step B: 创建 WFS session
|
||||
Skill({ skill: "workflow:session:start", args: `--auto "${structuredDesc}"` })
|
||||
// → 产出 sessionId (WFS-xxx) 和 session 目录
|
||||
|
||||
// Step C: 创建 .brainstorming/ 桥接文件
|
||||
const brainstormDir = `.workflow/active/${sessionId}/.brainstorming`
|
||||
Bash(`mkdir -p "${brainstormDir}/feature-specs"`)
|
||||
|
||||
// C.1: guidance-specification.md(action-planning-agent 最高优先读取)
|
||||
Write(`${brainstormDir}/guidance-specification.md`, `
|
||||
# ${specConfig.seed_analysis?.problem_statement || specConfig.topic} - Confirmed Guidance Specification
|
||||
|
||||
**Source**: spec-team session ${specConfig.session_id}
|
||||
**Generated**: ${new Date().toISOString()}
|
||||
**Spec Directory**: ${sessionFolder}
|
||||
|
||||
## 1. Project Positioning & Goals
|
||||
${extractSection(productBrief, "Vision")}
|
||||
${extractSection(productBrief, "Goals")}
|
||||
|
||||
## 2. Requirements Summary
|
||||
${extractSection(requirementsIndex, "Functional Requirements")}
|
||||
|
||||
## 3. Architecture Decisions
|
||||
${extractSection(architectureIndex, "Architecture Decision Records")}
|
||||
${extractSection(architectureIndex, "Technology Stack")}
|
||||
|
||||
## 4. Implementation Scope
|
||||
${extractSection(epicsIndex, "Epic Overview")}
|
||||
${extractSection(epicsIndex, "MVP Scope")}
|
||||
|
||||
## Feature Decomposition
|
||||
${extractSection(epicsIndex, "Traceability Matrix")}
|
||||
|
||||
## Appendix: Source Documents
|
||||
| Document | Path | Description |
|
||||
|----------|------|-------------|
|
||||
| Product Brief | ${sessionFolder}/spec/product-brief.md | Vision, goals, scope |
|
||||
| Requirements | ${sessionFolder}/spec/requirements/ | _index.md + REQ-*.md + NFR-*.md |
|
||||
| Architecture | ${sessionFolder}/spec/architecture/ | _index.md + ADR-*.md |
|
||||
| Epics | ${sessionFolder}/spec/epics/ | _index.md + EPIC-*.md |
|
||||
| Readiness Report | ${sessionFolder}/spec/readiness-report.md | Quality validation |
|
||||
`)
|
||||
|
||||
// C.2: feature-index.json(EPIC → Feature 映射)
|
||||
const features = epicFiles.map(epicFile => {
|
||||
const content = Read(epicFile)
|
||||
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/)
|
||||
const fm = fmMatch ? parseYAML(fmMatch[1]) : {}
|
||||
const basename = epicFile.replace(/.*[/\\]/, '').replace('.md', '')
|
||||
const epicNum = (fm.id || '').replace('EPIC-', '')
|
||||
const slug = basename.replace(/^EPIC-\d+-/, '')
|
||||
return {
|
||||
id: `F-${epicNum}`, slug, name: content.match(/^#\s+(.+)/m)?.[1] || '',
|
||||
priority: fm.mvp ? "High" : "Medium",
|
||||
spec_path: `${brainstormDir}/feature-specs/F-${epicNum}-${slug}.md`,
|
||||
source_epic: fm.id, source_file: epicFile
|
||||
}
|
||||
})
|
||||
Write(`${brainstormDir}/feature-specs/feature-index.json`, JSON.stringify({
|
||||
version: "1.0", source: "spec-team",
|
||||
spec_session: specConfig.session_id, features, cross_cutting_specs: []
|
||||
}, null, 2))
|
||||
|
||||
// C.3: Feature-spec 文件(EPIC → F-*.md 转换)
|
||||
features.forEach(feature => {
|
||||
const epicContent = Read(feature.source_file)
|
||||
Write(feature.spec_path, `
|
||||
# Feature Spec: ${feature.source_epic} - ${feature.name}
|
||||
|
||||
**Source**: ${feature.source_file}
|
||||
**Priority**: ${feature.priority === "High" ? "MVP" : "Post-MVP"}
|
||||
|
||||
## Description
|
||||
${extractSection(epicContent, "Description")}
|
||||
|
||||
## Stories
|
||||
${extractSection(epicContent, "Stories")}
|
||||
|
||||
## Requirements
|
||||
${extractSection(epicContent, "Requirements")}
|
||||
|
||||
## Architecture
|
||||
${extractSection(epicContent, "Architecture")}
|
||||
`)
|
||||
})
|
||||
|
||||
// Step D: 调用下游 workflow
|
||||
if (handoffChoice === 'full-plan') {
|
||||
Skill({ skill: "workflow:plan", args: `"${structuredDesc}"` })
|
||||
} else {
|
||||
Skill({ skill: "workflow:req-plan-with-file", args: `"${specConfig.seed_analysis?.problem_statement || specConfig.topic}"` })
|
||||
}
|
||||
}
|
||||
|
||||
if (handoffChoice === 'create-issues') {
|
||||
// 逐 EPIC 文件创建 issue
|
||||
epicFiles.forEach(epicFile => {
|
||||
const content = Read(epicFile)
|
||||
const title = content.match(/^#\s+(.+)/m)?.[1] || ''
|
||||
const description = content.match(/## Description\n([\s\S]*?)(?=\n## )/)?.[1]?.trim() || ''
|
||||
Skill({ skill: "issue:new", args: `"${title}: ${description}"` })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// === 关闭 → shutdown 给每个 teammate → TeamDelete() ===
|
||||
```
|
||||
|
||||
#### Helper Functions Reference (pseudocode)
|
||||
|
||||
```javascript
|
||||
// Extract a named ## section from a markdown document
|
||||
function extractSection(markdown, sectionName) {
|
||||
// Return content between ## {sectionName} and next ## heading
|
||||
const regex = new RegExp(`## ${sectionName}\\n([\\s\\S]*?)(?=\\n## |$)`)
|
||||
return markdown.match(regex)?.[1]?.trim() || ''
|
||||
}
|
||||
|
||||
// Parse YAML frontmatter string into object
|
||||
function parseYAML(yamlStr) {
|
||||
// Simple key-value parsing from YAML frontmatter
|
||||
const result = {}
|
||||
yamlStr.split('\n').forEach(line => {
|
||||
const match = line.match(/^(\w+):\s*(.+)/)
|
||||
if (match) result[match[1]] = match[2].replace(/^["']|["']$/g, '')
|
||||
})
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## Session State Tracking
|
||||
|
||||
At each key transition, update `team-session.json`:
|
||||
|
||||
```javascript
|
||||
// Helper: update session state
|
||||
function updateSession(sessionFolder, updates) {
|
||||
const session = JSON.parse(Read(`${sessionFolder}/team-session.json`))
|
||||
Object.assign(session, updates, { updated_at: new Date().toISOString() })
|
||||
Write(`${sessionFolder}/team-session.json`, JSON.stringify(session, null, 2))
|
||||
}
|
||||
|
||||
// On task completion:
|
||||
updateSession(sessionFolder, {
|
||||
completed_tasks: [...session.completed_tasks, taskPrefix],
|
||||
pipeline_progress: { ...session.pipeline_progress,
|
||||
[phase]: { ...session.pipeline_progress[phase], completed: session.pipeline_progress[phase].completed + 1 }
|
||||
}
|
||||
})
|
||||
|
||||
// On phase transition (spec → plan):
|
||||
updateSession(sessionFolder, { current_phase: 'plan' })
|
||||
|
||||
// On completion:
|
||||
updateSession(sessionFolder, { status: 'completed', completed_at: new Date().toISOString() })
|
||||
|
||||
// On user closes team:
|
||||
updateSession(sessionFolder, { status: 'completed', completed_at: new Date().toISOString() })
|
||||
```
|
||||
|
||||
## Session File Structure
|
||||
|
||||
```
|
||||
.workflow/.team/TLS-{slug}-{YYYY-MM-DD}/
|
||||
├── team-session.json # Session state (resume support)
|
||||
├── spec/ # Spec artifacts
|
||||
│ ├── spec-config.json
|
||||
│ ├── discovery-context.json
|
||||
│ ├── product-brief.md
|
||||
│ ├── requirements/ # _index.md + REQ-*.md + NFR-*.md
|
||||
│ ├── architecture/ # _index.md + ADR-*.md
|
||||
│ ├── epics/ # _index.md + EPIC-*.md
|
||||
│ ├── readiness-report.md
|
||||
│ └── spec-summary.md
|
||||
├── discussions/ # Discussion records
|
||||
│ └── discuss-001..006.md
|
||||
└── plan/ # Plan artifacts
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── 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 无法完成 | 降级为简化模式 |
|
||||
@@ -1,236 +0,0 @@
|
||||
# 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 |
|
||||
| **Coverage** | Requirement completeness vs original intent, scope drift, gap detection | Requirements Analyst |
|
||||
|
||||
## Discussion Round Configuration
|
||||
|
||||
| Round | Artifact | Key Perspectives | Focus |
|
||||
|-------|----------|-----------------|-------|
|
||||
| DISCUSS-001 | discovery-context | product + risk + **coverage** | Scope confirmation, direction, initial coverage check |
|
||||
| DISCUSS-002 | product-brief | product + technical + quality + **coverage** | Positioning, feasibility, requirement coverage |
|
||||
| DISCUSS-003 | requirements | quality + product + **coverage** | Completeness, priority, gap detection |
|
||||
| DISCUSS-004 | architecture | technical + risk | Tech choices, security |
|
||||
| DISCUSS-005 | epics | product + technical + quality + **coverage** | MVP scope, estimation, requirement tracing |
|
||||
| DISCUSS-006 | readiness-report | all 5 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: 'spec/discovery-context.json', type: 'json', outputFile: 'discuss-001-scope.md', perspectives: ['product', 'risk', 'coverage'], label: '范围讨论' },
|
||||
2: { artifact: 'spec/product-brief.md', type: 'md', outputFile: 'discuss-002-brief.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Brief评审' },
|
||||
3: { artifact: 'spec/requirements/_index.md', type: 'md', outputFile: 'discuss-003-requirements.md', perspectives: ['quality', 'product', 'coverage'], label: '需求讨论' },
|
||||
4: { artifact: 'spec/architecture/_index.md', type: 'md', outputFile: 'discuss-004-architecture.md', perspectives: ['technical', 'risk'], label: '架构讨论' },
|
||||
5: { artifact: 'spec/epics/_index.md', type: 'md', outputFile: 'discuss-005-epics.md', perspectives: ['product', 'technical', 'quality', 'coverage'], label: 'Epics讨论' },
|
||||
6: { artifact: 'spec/readiness-report.md', type: 'md', outputFile: 'discuss-006-final.md', perspectives: ['product', 'technical', 'quality', 'risk', 'coverage'], 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.
|
||||
- **Coverage Perspective** (gemini): Compare current artifact against original requirements in discovery-context.json. Identify covered_requirements[], partial_requirements[], missing_requirements[], scope_creep[]. Rate coverage 1-5. **If missing_requirements is non-empty, flag as critical divergence.**
|
||||
|
||||
Each CLI call produces structured critique with: strengths[], weaknesses[], suggestions[], rating. Coverage perspective additionally outputs: covered_requirements[], missing_requirements[], scope_creep[].
|
||||
|
||||
### 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 coverage gaps from coverage perspective (if present)
|
||||
const coverageResult = perspectiveResults.find(p => p.perspective === 'coverage')
|
||||
if (coverageResult?.missing_requirements?.length > 0) {
|
||||
synthesis.coverage_gaps = coverageResult.missing_requirements
|
||||
synthesis.divergent_views.push({
|
||||
topic: 'requirement_coverage_gap',
|
||||
description: `${coverageResult.missing_requirements.length} requirements from discovery-context not covered: ${coverageResult.missing_requirements.join(', ')}`,
|
||||
severity: 'high',
|
||||
source: 'coverage'
|
||||
})
|
||||
}
|
||||
// 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 |
|
||||
@@ -1,312 +0,0 @@
|
||||
# Role: executor
|
||||
|
||||
Code implementation following approved plans. Reads plan files, routes to selected execution backend (Agent/Codex/Gemini), self-validates, and reports completion.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `executor`
|
||||
- **Task Prefix**: `IMPL-*`
|
||||
- **Responsibility**: Load plan → Route to backend → 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 Backends
|
||||
|
||||
| Backend | Tool | Invocation | Mode |
|
||||
|---------|------|------------|------|
|
||||
| `agent` | code-developer subagent | `Task({ subagent_type: "code-developer" })` | 同步 |
|
||||
| `codex` | Codex CLI | `ccw cli --tool codex --mode write` | 后台 |
|
||||
| `gemini` | Gemini CLI | `ccw cli --tool gemini --mode write` | 后台 |
|
||||
|
||||
## Execution Method Resolution
|
||||
|
||||
从 IMPL-* 任务 description 中解析执行方式(coordinator 在创建任务时已写入):
|
||||
|
||||
```javascript
|
||||
function resolveExecutor(taskDesc, taskCount) {
|
||||
const methodMatch = taskDesc.match(/execution_method:\s*(Agent|Codex|Gemini|Auto)/i)
|
||||
const method = methodMatch ? methodMatch[1] : 'Auto'
|
||||
|
||||
if (method.toLowerCase() === 'auto') {
|
||||
return taskCount <= 3 ? 'agent' : 'codex'
|
||||
}
|
||||
return method.toLowerCase() // 'agent' | 'codex' | 'gemini'
|
||||
}
|
||||
|
||||
function resolveCodeReview(taskDesc) {
|
||||
const reviewMatch = taskDesc.match(/code_review:\s*(\S+)/i)
|
||||
return reviewMatch ? reviewMatch[1] : 'Skip'
|
||||
}
|
||||
```
|
||||
|
||||
## 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\/[^\s]+\/plan\/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))
|
||||
|
||||
// Resolve execution method
|
||||
const executor = resolveExecutor(task.description, plan.task_count || plan.task_ids?.length || 0)
|
||||
const codeReview = resolveCodeReview(task.description)
|
||||
```
|
||||
|
||||
### 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 (Multi-Backend Routing)
|
||||
|
||||
```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')}
|
||||
`
|
||||
}
|
||||
|
||||
function buildBatchPrompt(batch) {
|
||||
const taskPrompts = batch.tasks.map(buildExecutionPrompt).join('\n\n---\n')
|
||||
return `## Goal\n${plan.summary}\n\n## Tasks\n${taskPrompts}\n\n## Context\n### Project Guidelines\n@.workflow/project-guidelines.json\n\nComplete each task according to its "Done when" checklist.`
|
||||
}
|
||||
|
||||
const changedFiles = []
|
||||
const sessionId = task.description.match(/TLS-[\w-]+/)?.[0] || 'lifecycle'
|
||||
|
||||
for (const batch of batches) {
|
||||
const batchPrompt = buildBatchPrompt(batch)
|
||||
const batchId = `${sessionId}-B${batches.indexOf(batch) + 1}`
|
||||
|
||||
if (batch.tasks.length === 1 && isSimpleTask(batch.tasks[0]) && executor === 'agent') {
|
||||
// Simple task + Agent mode: 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 if (executor === 'agent') {
|
||||
// Agent execution (synchronous)
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: batch.tasks.map(t => t.title).join(' | '),
|
||||
prompt: batchPrompt
|
||||
})
|
||||
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||
} else if (executor === 'codex') {
|
||||
// Codex CLI execution (background)
|
||||
Bash(
|
||||
`ccw cli -p "${batchPrompt}" --tool codex --mode write --id ${batchId}`,
|
||||
{ run_in_background: true }
|
||||
)
|
||||
// STOP — CLI 后台执行,等待 task hook callback
|
||||
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||
} else if (executor === 'gemini') {
|
||||
// Gemini CLI execution (background)
|
||||
Bash(
|
||||
`ccw cli -p "${batchPrompt}" --tool gemini --mode write --id ${batchId}`,
|
||||
{ run_in_background: true }
|
||||
)
|
||||
// STOP — CLI 后台执行,等待 task hook callback
|
||||
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完成 (${executor}): ${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`)
|
||||
|
||||
// Optional: Code review (if configured by coordinator)
|
||||
if (codeReview !== 'Skip') {
|
||||
if (codeReview === 'Gemini Review' || codeReview === 'Gemini') {
|
||||
Bash(`ccw cli -p "PURPOSE: Code review for IMPL changes against plan convergence criteria
|
||||
TASK: • Verify convergence criteria • Check test coverage • Analyze code quality
|
||||
MODE: analysis
|
||||
CONTEXT: @**/* | Memory: Review lifecycle IMPL execution
|
||||
EXPECTED: Quality assessment with issue identification
|
||||
CONSTRAINTS: analysis=READ-ONLY" --tool gemini --mode analysis --id ${sessionId}-review`,
|
||||
{ run_in_background: true })
|
||||
} else if (codeReview === 'Codex Review' || codeReview === 'Codex') {
|
||||
Bash(`ccw cli --tool codex --mode review --uncommitted`,
|
||||
{ run_in_background: 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完成 (${executor}): ${[...new Set(changedFiles)].length}个文件变更, syntax=${hasSyntaxErrors ? 'errors' : 'clean'}`
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Implementation Complete
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Executor**: ${executor}
|
||||
**Code Review**: ${codeReview}
|
||||
|
||||
### 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'}
|
||||
${executor !== 'agent' ? `- CLI Resume ID: ${sessionId}-B*` : ''}
|
||||
|
||||
Implementation is ready for testing and review.`,
|
||||
summary: `IMPL complete (${executor}): ${[...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 |
|
||||
| Unknown execution_method | Fallback to `agent` with warning |
|
||||
| Syntax errors after implementation | Attempt auto-fix, report remaining errors |
|
||||
| Agent (code-developer) failure | Retry once, then attempt direct implementation |
|
||||
| CLI (Codex/Gemini) failure | Provide resume command with fixed ID, report error |
|
||||
| CLI timeout | Use fixed ID `${sessionId}-B*` for resume |
|
||||
| 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 |
|
||||
@@ -1,298 +0,0 @@
|
||||
# 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/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/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 1.5: Load Spec Context (Full-Lifecycle Mode)
|
||||
|
||||
```javascript
|
||||
// Extract session folder from task description (set by coordinator)
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : `.workflow/.team/default`
|
||||
const planDir = `${sessionFolder}/plan`
|
||||
Bash(`mkdir -p ${planDir}`)
|
||||
|
||||
// Check if spec directory exists (full-lifecycle mode)
|
||||
const specDir = `${sessionFolder}/spec`
|
||||
let specContext = null
|
||||
try {
|
||||
const reqIndex = Read(`${specDir}/requirements/_index.md`)
|
||||
const archIndex = Read(`${specDir}/architecture/_index.md`)
|
||||
const epicsIndex = Read(`${specDir}/epics/_index.md`)
|
||||
const specConfig = JSON.parse(Read(`${specDir}/spec-config.json`))
|
||||
specContext = { reqIndex, archIndex, epicsIndex, specConfig }
|
||||
} catch { /* impl-only mode has no spec */ }
|
||||
```
|
||||
|
||||
### Phase 2: Multi-Angle Exploration
|
||||
|
||||
```javascript
|
||||
|
||||
// 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(`${planDir}/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**: ${planDir}/exploration-${angle}.json
|
||||
|
||||
## Assigned Context
|
||||
- **Exploration Angle**: ${angle}
|
||||
- **Task Description**: ${task.description}
|
||||
- **Spec Context**: ${specContext ? 'Available — use spec/requirements, spec/architecture, spec/epics for informed exploration' : 'Not available (impl-only mode)'}
|
||||
- **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: ${planDir}/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: `${planDir}/exploration-${angle}.json`
|
||||
}))
|
||||
}
|
||||
Write(`${planDir}/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 ${planDir}/.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: ${planDir}/plan.json + ${planDir}/.task/TASK-*.json
|
||||
Schema: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json
|
||||
Task Description: ${task.description}
|
||||
Explorations: ${explorationManifest}
|
||||
Complexity: ${complexity}
|
||||
${specContext ? `Spec Context:
|
||||
- Requirements: ${specContext.reqIndex.substring(0, 500)}
|
||||
- Architecture: ${specContext.archIndex.substring(0, 500)}
|
||||
- Epics: ${specContext.epicsIndex.substring(0, 500)}
|
||||
Reference REQ-* IDs, follow ADR decisions, reuse Epic/Story decomposition.` : ''}
|
||||
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(`${planDir}/plan.json`))
|
||||
const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${planDir}/.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: `${planDir}/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
|
||||
${planDir}/plan.json
|
||||
Task Files: ${planDir}/.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
|
||||
|
||||
```
|
||||
{sessionFolder}/plan/
|
||||
├── exploration-{angle}.json
|
||||
├── explorations-manifest.json
|
||||
├── planning-context.md
|
||||
├── plan.json
|
||||
└── .task/
|
||||
└── TASK-*.json
|
||||
```
|
||||
|
||||
> **Note**: `sessionFolder` is extracted from task description (`Session: .workflow/.team/TLS-xxx`). Plan outputs go to `plan/` subdirectory. In full-lifecycle mode, spec products are available at `../spec/`.
|
||||
|
||||
## 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 |
|
||||
@@ -1,622 +0,0 @@
|
||||
# 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\/[^\s]+\/plan\/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() : ''
|
||||
|
||||
// 加载质量门禁标准(引用 spec-generator 共享资源)
|
||||
let qualityGates = null
|
||||
try { qualityGates = Read('../specs/quality-gates.md') } catch {}
|
||||
|
||||
// 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/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/spec/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/spec/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/spec/epics/_index.md`) } catch {}
|
||||
|
||||
// Load individual documents
|
||||
Glob({ pattern: `${sessionFolder}/spec/requirements/REQ-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/requirements/NFR-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/architecture/ADR-*.md` }).forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/spec/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, requirementCoverage: 0 }
|
||||
|
||||
// Completeness (25%): all sections present with content
|
||||
function scoreCompleteness(docs) {
|
||||
let score = 0
|
||||
const issues = []
|
||||
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; else issues.push(`Missing: ${c.name}`) })
|
||||
|
||||
// 增强: section 内容检查(不仅检查文件是否存在,还检查关键 section 是否有实质内容)
|
||||
if (docs.productBrief) {
|
||||
const briefSections = ['## Vision', '## Problem Statement', '## Target Users', '## Goals', '## Scope']
|
||||
const missingSections = briefSections.filter(s => !docs.productBrief.includes(s))
|
||||
if (missingSections.length > 0) {
|
||||
score -= missingSections.length * 3
|
||||
issues.push(`Product Brief missing sections: ${missingSections.join(', ')}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (docs.requirementsIndex) {
|
||||
const reqSections = ['## Functional Requirements', '## Non-Functional Requirements', '## MoSCoW Summary']
|
||||
const missingReqSections = reqSections.filter(s => !docs.requirementsIndex.includes(s))
|
||||
if (missingReqSections.length > 0) {
|
||||
score -= missingReqSections.length * 3
|
||||
issues.push(`Requirements index missing sections: ${missingReqSections.join(', ')}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (docs.architectureIndex) {
|
||||
const archSections = ['## Architecture Decision Records', '## Technology Stack']
|
||||
const missingArchSections = archSections.filter(s => !docs.architectureIndex.includes(s))
|
||||
if (missingArchSections.length > 0) {
|
||||
score -= missingArchSections.length * 3
|
||||
issues.push(`Architecture index missing sections: ${missingArchSections.join(', ')}`)
|
||||
}
|
||||
if (!docs.architectureIndex.includes('```mermaid')) {
|
||||
score -= 5
|
||||
issues.push('Architecture index missing Mermaid component diagram')
|
||||
}
|
||||
}
|
||||
|
||||
if (docs.epicsIndex) {
|
||||
const epicsSections = ['## Epic Overview', '## MVP Scope']
|
||||
const missingEpicsSections = epicsSections.filter(s => !docs.epicsIndex.includes(s))
|
||||
if (missingEpicsSections.length > 0) {
|
||||
score -= missingEpicsSections.length * 3
|
||||
issues.push(`Epics index missing sections: ${missingEpicsSections.join(', ')}`)
|
||||
}
|
||||
if (!docs.epicsIndex.includes('```mermaid')) {
|
||||
score -= 5
|
||||
issues.push('Epics index missing Mermaid dependency diagram')
|
||||
}
|
||||
}
|
||||
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
// 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 }
|
||||
}
|
||||
|
||||
// Requirement Coverage (20%): original requirements → document mapping
|
||||
function scoreRequirementCoverage(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
if (!docs.discoveryContext) {
|
||||
return { score: 0, issues: ['discovery-context.json missing, cannot verify requirement coverage'] }
|
||||
}
|
||||
const context = typeof docs.discoveryContext === 'string' ? JSON.parse(docs.discoveryContext) : docs.discoveryContext
|
||||
const dimensions = context.seed_analysis?.exploration_dimensions || []
|
||||
const constraints = context.seed_analysis?.constraints || []
|
||||
const userSupplements = context.seed_analysis?.user_supplements || ''
|
||||
const allRequirements = [...dimensions, ...constraints]
|
||||
if (userSupplements) allRequirements.push(userSupplements)
|
||||
|
||||
if (allRequirements.length === 0) {
|
||||
return { score: 100, issues: [] } // No requirements to check
|
||||
}
|
||||
|
||||
const allDocContent = [docs.productBrief, docs.requirementsIndex, docs.architectureIndex, docs.epicsIndex,
|
||||
...docs.requirements, ...docs.adrs, ...docs.epics].filter(Boolean).join('\n').toLowerCase()
|
||||
|
||||
let covered = 0
|
||||
for (const req of allRequirements) {
|
||||
const keywords = req.toLowerCase().split(/[\s,;]+/).filter(w => w.length > 2)
|
||||
const isCovered = keywords.some(kw => allDocContent.includes(kw))
|
||||
if (isCovered) { covered++ }
|
||||
else { issues.push(`Requirement not covered in documents: "${req}"`) }
|
||||
}
|
||||
|
||||
score = Math.round((covered / allRequirements.length) * 100)
|
||||
return { score, issues }
|
||||
}
|
||||
|
||||
const completenessResult = scoreCompleteness(documents)
|
||||
const consistencyResult = scoreConsistency(documents)
|
||||
const traceabilityResult = scoreTraceability(documents)
|
||||
const depthResult = scoreDepth(documents)
|
||||
const coverageResult = scoreRequirementCoverage(documents)
|
||||
|
||||
scores.completeness = completenessResult.score
|
||||
scores.consistency = consistencyResult.score
|
||||
scores.traceability = traceabilityResult.score
|
||||
scores.depth = depthResult.score
|
||||
scores.requirementCoverage = coverageResult.score
|
||||
|
||||
const overallScore = (scores.completeness + scores.consistency + scores.traceability + scores.depth + scores.requirementCoverage) / 5
|
||||
const qualityGate = (overallScore >= 80 && scores.requirementCoverage >= 70) ? 'PASS' :
|
||||
(overallScore < 60 || scores.requirementCoverage < 50) ? 'FAIL' : 'REVIEW'
|
||||
const allSpecIssues = [...completenessResult.issues, ...consistencyResult.issues, ...traceabilityResult.issues, ...depthResult.issues, ...coverageResult.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}% | 20% |
|
||||
| Consistency | ${scores.consistency}% | 20% |
|
||||
| Traceability | ${scores.traceability}% | 20% |
|
||||
| Depth | ${scores.depth}% | 20% |
|
||||
| Requirement Coverage | ${scores.requirementCoverage}% | 20% |
|
||||
| **Overall** | **${overallScore.toFixed(1)}%** | **100%** |
|
||||
|
||||
## Quality Gate: ${qualityGate}
|
||||
|
||||
## Per-Phase Quality Gates
|
||||
${qualityGates ? `_(Applied from ../specs/quality-gates.md)_
|
||||
|
||||
### Phase 2 (Product Brief)
|
||||
- Vision statement: ${docs.productBrief?.includes('## Vision') ? 'PASS' : 'MISSING'}
|
||||
- Problem statement specificity: ${docs.productBrief?.match(/## Problem/)?.length ? 'PASS' : 'MISSING'}
|
||||
- Target users >= 1: ${docs.productBrief?.includes('## Target Users') ? 'PASS' : 'MISSING'}
|
||||
- Measurable goals >= 2: ${docs.productBrief?.includes('## Goals') ? 'PASS' : 'MISSING'}
|
||||
|
||||
### Phase 3 (Requirements)
|
||||
- Functional requirements >= 3: ${docs.requirements.length >= 3 ? 'PASS' : 'FAIL (' + docs.requirements.length + ')'}
|
||||
- Acceptance criteria present: ${docs.requirements.some(r => /acceptance|criteria/i.test(r)) ? 'PASS' : 'MISSING'}
|
||||
- MoSCoW priority tags: ${docs.requirementsIndex?.includes('Must') ? 'PASS' : 'MISSING'}
|
||||
|
||||
### Phase 4 (Architecture)
|
||||
- Component diagram: ${docs.architectureIndex?.includes('mermaid') ? 'PASS' : 'MISSING'}
|
||||
- ADR with alternatives: ${docs.adrs.some(a => /alternative|option/i.test(a)) ? 'PASS' : 'MISSING'}
|
||||
- Tech stack specified: ${docs.architectureIndex?.includes('Technology') ? 'PASS' : 'MISSING'}
|
||||
|
||||
### Phase 5 (Epics)
|
||||
- MVP subset tagged: ${docs.epics.some(e => /mvp:\s*true/i.test(e)) ? 'PASS' : 'MISSING'}
|
||||
- Dependency map: ${docs.epicsIndex?.includes('mermaid') ? 'PASS' : 'MISSING'}
|
||||
- Story sizing: ${docs.epics.some(e => /\b[SMLX]{1,2}\b|Small|Medium|Large/.test(e)) ? 'PASS' : 'MISSING'}
|
||||
` : '_(quality-gates.md not loaded)_'}
|
||||
|
||||
## 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}/spec/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/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}/覆盖率${scores.requirementCoverage})`,
|
||||
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}% |
|
||||
| 需求覆盖率 | ${scores.requirementCoverage}% |
|
||||
|
||||
### 问题列表 (${allSpecIssues.length})
|
||||
${allSpecIssues.map(i => '- ' + i).join('\n') || '无问题'}
|
||||
|
||||
### 文档清单
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (typeof v === 'boolean' ? (v ? '✓' : '✗') : v)).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- 就绪报告: ${sessionFolder}/spec/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec/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,294 +0,0 @@
|
||||
# Role: tester
|
||||
|
||||
Adaptive test-fix cycle with progressive testing strategy. Detects test framework, applies multi-strategy fixes, and reports results to coordinator.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `tester`
|
||||
- **Task Prefix**: `TEST-*`
|
||||
- **Responsibility**: Detect Framework → Run Tests → Fix Cycle → Report Results
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| 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 |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// 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" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
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`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('TEST-') &&
|
||||
t.owner === 'tester' &&
|
||||
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: Test Framework Detection
|
||||
|
||||
```javascript
|
||||
function detectTestFramework() {
|
||||
// Check package.json
|
||||
try {
|
||||
const pkg = JSON.parse(Read('package.json'))
|
||||
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
||||
if (deps.vitest) return { framework: 'vitest', command: 'npx vitest run' }
|
||||
if (deps.jest) return { framework: 'jest', command: 'npx jest' }
|
||||
if (deps.mocha) return { framework: 'mocha', command: 'npx mocha' }
|
||||
} catch {}
|
||||
|
||||
// Check pyproject.toml / pytest
|
||||
try {
|
||||
const pyproject = Read('pyproject.toml')
|
||||
if (pyproject.includes('pytest')) return { framework: 'pytest', command: 'pytest' }
|
||||
} catch {}
|
||||
|
||||
return { framework: 'unknown', command: 'npm test' }
|
||||
}
|
||||
|
||||
const testConfig = detectTestFramework()
|
||||
|
||||
// Locate affected test files from changed files
|
||||
function findAffectedTests(changedFiles) {
|
||||
const testFiles = []
|
||||
for (const file of changedFiles) {
|
||||
const testVariants = [
|
||||
file.replace(/\/src\//, '/tests/').replace(/\.(ts|js|tsx|jsx)$/, '.test.$1'),
|
||||
file.replace(/\/src\//, '/__tests__/').replace(/\.(ts|js|tsx|jsx)$/, '.test.$1'),
|
||||
file.replace(/\.(ts|js|tsx|jsx)$/, '.test.$1'),
|
||||
file.replace(/\.(ts|js|tsx|jsx)$/, '.spec.$1')
|
||||
]
|
||||
for (const variant of testVariants) {
|
||||
const exists = Bash(`test -f "${variant}" && echo exists || true`)
|
||||
if (exists.includes('exists')) testFiles.push(variant)
|
||||
}
|
||||
}
|
||||
return [...new Set(testFiles)]
|
||||
}
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
### Phase 3: Test Execution & Fix Cycle
|
||||
|
||||
```javascript
|
||||
const MAX_ITERATIONS = 10
|
||||
const PASS_RATE_TARGET = 95
|
||||
|
||||
let currentPassRate = 0
|
||||
let previousPassRate = 0
|
||||
const iterationHistory = []
|
||||
|
||||
for (let iteration = 1; iteration <= MAX_ITERATIONS; iteration++) {
|
||||
// Strategy selection
|
||||
const strategy = selectStrategy(iteration, currentPassRate, previousPassRate, iterationHistory)
|
||||
|
||||
// Determine test scope
|
||||
const isFullSuite = iteration === MAX_ITERATIONS || currentPassRate >= PASS_RATE_TARGET
|
||||
const testCommand = isFullSuite
|
||||
? testConfig.command
|
||||
: `${testConfig.command} ${affectedTests.join(' ')}`
|
||||
|
||||
// Run tests
|
||||
const testOutput = Bash(`${testCommand} 2>&1 || true`, { timeout: 300000 })
|
||||
|
||||
// Parse results
|
||||
const results = parseTestResults(testOutput, testConfig.framework)
|
||||
previousPassRate = currentPassRate
|
||||
currentPassRate = results.passRate
|
||||
|
||||
iterationHistory.push({
|
||||
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) {
|
||||
const fullOutput = Bash(`${testConfig.command} 2>&1 || true`, { timeout: 300000 })
|
||||
const fullResults = parseTestResults(fullOutput, testConfig.framework)
|
||||
currentPassRate = fullResults.passRate
|
||||
if (currentPassRate >= PASS_RATE_TARGET) break
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (iteration >= MAX_ITERATIONS) break
|
||||
|
||||
// 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) {
|
||||
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'
|
||||
}
|
||||
|
||||
// Fix application
|
||||
function applyFixes(failedTests, strategy, testOutput) {
|
||||
switch (strategy) {
|
||||
case 'conservative':
|
||||
// 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
|
||||
break
|
||||
case 'surgical':
|
||||
// Minimal changes, consider rollback - fix most critical failure only
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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]) }
|
||||
total = passed + failed
|
||||
}
|
||||
return { passed, failed, total, passRate: total > 0 ? Math.round((passed / total) * 100) : 100, failedTests }
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Result Analysis
|
||||
|
||||
```javascript
|
||||
function classifyFailures(failedTests) {
|
||||
return failedTests.map(test => {
|
||||
const testLower = test.toLowerCase()
|
||||
let severity = 'low'
|
||||
if (/auth|security|permission|login|password/.test(testLower)) severity = 'critical'
|
||||
else if (/core|main|primary|data|state/.test(testLower)) severity = 'high'
|
||||
else if (/edge|flaky|timeout|env/.test(testLower)) severity = 'low'
|
||||
else severity = 'medium'
|
||||
return { test, severity }
|
||||
})
|
||||
}
|
||||
|
||||
const classifiedFailures = classifyFailures(iterationHistory[iterationHistory.length - 1]?.failed_tests || [])
|
||||
const hasCriticalFailures = classifiedFailures.some(f => f.severity === 'critical')
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
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",
|
||||
content: `## Test Results
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Status**: ${success ? 'PASSED' : 'NEEDS ATTENTION'}
|
||||
|
||||
### Summary
|
||||
- **Pass Rate**: ${currentPassRate}% (target: ${PASS_RATE_TARGET}%)
|
||||
- **Iterations**: ${iterationHistory.length}/${MAX_ITERATIONS}
|
||||
- **Total Tests**: ${finalIteration?.total || 0}
|
||||
- **Passed**: ${finalIteration?.passed || 0}
|
||||
- **Failed**: ${finalIteration?.total - finalIteration?.passed || 0}
|
||||
|
||||
### Strategy History
|
||||
${iterationHistory.map(h => `- Iteration ${h.iteration}: ${h.strategy} → ${h.pass_rate}%`).join('\n')}
|
||||
|
||||
${!success ? `### Remaining Failures
|
||||
${classifiedFailures.map(f => `- [${f.severity.toUpperCase()}] ${f.test}`).join('\n')}
|
||||
|
||||
${hasCriticalFailures ? '**CRITICAL failures detected - immediate attention required**' : ''}` : '### All tests passing'}`,
|
||||
summary: `Tests: ${currentPassRate}% pass rate (${iterationHistory.length} iterations)`
|
||||
})
|
||||
|
||||
if (success) {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
} else {
|
||||
// Keep in_progress, coordinator decides next steps
|
||||
}
|
||||
|
||||
// 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 |
|
||||
@@ -1,739 +0,0 @@
|
||||
# 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/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}/spec/discovery-context.json`) } catch {}
|
||||
}
|
||||
if (['requirements', 'architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.productBrief = Read(`${sessionFolder}/spec/product-brief.md`) } catch {}
|
||||
}
|
||||
if (['architecture', 'epics'].includes(docType)) {
|
||||
try { priorDocs.requirementsIndex = Read(`${sessionFolder}/spec/requirements/_index.md`) } catch {}
|
||||
}
|
||||
if (docType === 'epics') {
|
||||
try { priorDocs.architectureIndex = Read(`${sessionFolder}/spec/architecture/_index.md`) } catch {}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Document Generation (type-specific)
|
||||
|
||||
**前置步骤(所有类型共用)**:
|
||||
|
||||
```javascript
|
||||
// 1. 加载格式规范
|
||||
const docStandards = Read('../specs/document-standards.md')
|
||||
|
||||
// 2. 加载对应 template 文件(路径见 SKILL.md Shared Spec Resources)
|
||||
const templateMap = {
|
||||
'product-brief': '../templates/product-brief.md',
|
||||
'requirements': '../templates/requirements-prd.md',
|
||||
'architecture': '../templates/architecture-doc.md',
|
||||
'epics': '../templates/epics-template.md'
|
||||
}
|
||||
const template = Read(templateMap[docType])
|
||||
|
||||
// 3. 构建 sharedContext
|
||||
const seedAnalysis = specConfig?.seed_analysis || discoveryContext?.seed_analysis || {}
|
||||
const sharedContext = `
|
||||
SEED: ${specConfig?.topic || ''}
|
||||
PROBLEM: ${seedAnalysis.problem_statement || ''}
|
||||
TARGET USERS: ${(seedAnalysis.target_users || []).join(', ')}
|
||||
DOMAIN: ${seedAnalysis.domain || ''}
|
||||
CONSTRAINTS: ${(seedAnalysis.constraints || []).join(', ')}
|
||||
FOCUS AREAS: ${(specConfig?.focus_areas || []).join(', ')}
|
||||
${priorDocs.discoveryContext ? `
|
||||
CODEBASE CONTEXT:
|
||||
- Existing patterns: ${JSON.parse(priorDocs.discoveryContext).existing_patterns?.slice(0,5).join(', ') || 'none'}
|
||||
- Tech stack: ${JSON.stringify(JSON.parse(priorDocs.discoveryContext).tech_stack || {})}
|
||||
` : ''}`
|
||||
|
||||
// 4. 路由到具体类型
|
||||
```
|
||||
|
||||
#### DRAFT-001: Product Brief
|
||||
|
||||
3 路并行 CLI 分析(产品视角/技术视角/用户视角),综合后生成 product-brief.md。
|
||||
|
||||
```javascript
|
||||
if (docType === 'product-brief') {
|
||||
// === 并行 CLI 分析 ===
|
||||
|
||||
// 产品视角 (Gemini)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Product analysis for specification - identify market fit, user value, and success criteria.
|
||||
Success: Clear vision, measurable goals, competitive positioning.
|
||||
|
||||
${sharedContext}
|
||||
|
||||
TASK:
|
||||
- Define product vision (1-3 sentences, aspirational)
|
||||
- Analyze market/competitive landscape
|
||||
- Define 3-5 measurable success metrics
|
||||
- Identify scope boundaries (in-scope vs out-of-scope)
|
||||
- Assess user value proposition
|
||||
- List assumptions that need validation
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Structured product analysis with: vision, goals with metrics, scope, competitive positioning, assumptions
|
||||
CONSTRAINTS: Focus on 'what' and 'why', not 'how'
|
||||
" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// 技术视角 (Codex)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Technical feasibility analysis for specification - assess implementation viability and constraints.
|
||||
Success: Clear technical constraints, integration complexity, technology recommendations.
|
||||
|
||||
${sharedContext}
|
||||
|
||||
TASK:
|
||||
- Assess technical feasibility of the core concept
|
||||
- Identify technical constraints and blockers
|
||||
- Evaluate integration complexity with existing systems
|
||||
- Recommend technology approach (high-level)
|
||||
- Identify technical risks and dependencies
|
||||
- Estimate complexity: simple/moderate/complex
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Technical analysis with: feasibility assessment, constraints, integration complexity, tech recommendations, risks
|
||||
CONSTRAINTS: Focus on feasibility and constraints, not detailed architecture
|
||||
" --tool codex --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// 用户视角 (Claude)
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: User experience analysis for specification - understand user journeys, pain points, and UX considerations.
|
||||
Success: Clear user personas, journey maps, UX requirements.
|
||||
|
||||
${sharedContext}
|
||||
|
||||
TASK:
|
||||
- Elaborate user personas with goals and frustrations
|
||||
- Map primary user journey (happy path)
|
||||
- Identify key pain points in current experience
|
||||
- Define UX success criteria
|
||||
- List accessibility and usability considerations
|
||||
- Suggest interaction patterns
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: User analysis with: personas, journey map, pain points, UX criteria, interaction recommendations
|
||||
CONSTRAINTS: Focus on user needs and experience, not implementation
|
||||
" --tool claude --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// STOP: Wait for all 3 CLI results
|
||||
|
||||
// === 综合三视角 ===
|
||||
const synthesis = {
|
||||
convergent_themes: [], // 三视角一致的主题
|
||||
conflicts: [], // 视角冲突点
|
||||
product_insights: [], // 产品视角独特洞察
|
||||
technical_insights: [], // 技术视角独特洞察
|
||||
user_insights: [] // 用户视角独特洞察
|
||||
}
|
||||
|
||||
// === 整合讨论反馈 ===
|
||||
if (discussionFeedback) {
|
||||
// 从 discuss-001-scope.md 提取共识和调整建议
|
||||
// 将讨论结论融入 synthesis
|
||||
}
|
||||
|
||||
// === 按 template 生成文档 ===
|
||||
const frontmatter = `---
|
||||
session_id: ${specConfig?.session_id || 'unknown'}
|
||||
phase: 2
|
||||
document_type: product-brief
|
||||
status: draft
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
dependencies:
|
||||
- spec-config.json
|
||||
- discovery-context.json
|
||||
---`
|
||||
|
||||
// 填充 template 中所有 section: Vision, Problem Statement, Target Users, Goals, Scope
|
||||
// 应用 document-standards.md 格式规范
|
||||
|
||||
Write(`${sessionFolder}/spec/product-brief.md`, `${frontmatter}\n\n${filledContent}`)
|
||||
outputPath = 'spec/product-brief.md'
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-002: Requirements/PRD
|
||||
|
||||
通过 Gemini CLI 扩展需求,生成 REQ-NNN + NFR-{type}-NNN 文件。
|
||||
|
||||
```javascript
|
||||
if (docType === 'requirements') {
|
||||
// === 需求扩展 CLI ===
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Generate detailed functional and non-functional requirements from product brief.
|
||||
Success: Complete PRD with testable acceptance criteria for every requirement.
|
||||
|
||||
PRODUCT BRIEF CONTEXT:
|
||||
${priorDocs.productBrief?.slice(0, 3000) || ''}
|
||||
|
||||
${sharedContext}
|
||||
|
||||
TASK:
|
||||
- For each goal in the product brief, generate 3-7 functional requirements
|
||||
- Each requirement must have:
|
||||
- Unique ID: REQ-NNN (zero-padded)
|
||||
- Clear title
|
||||
- Detailed description
|
||||
- User story: As a [persona], I want [action] so that [benefit]
|
||||
- 2-4 specific, testable acceptance criteria
|
||||
- Generate non-functional requirements:
|
||||
- Performance (response times, throughput)
|
||||
- Security (authentication, authorization, data protection)
|
||||
- Scalability (user load, data volume)
|
||||
- Usability (accessibility, learnability)
|
||||
- Assign MoSCoW priority: Must/Should/Could/Won't
|
||||
- Output structure per requirement: ID, title, description, user_story, acceptance_criteria[], priority, traces
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Structured requirements with: ID, title, description, user story, acceptance criteria, priority, traceability to goals
|
||||
CONSTRAINTS: Every requirement must be specific enough to estimate and test. No vague requirements.
|
||||
" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Wait for CLI result
|
||||
|
||||
// === 整合讨论反馈 ===
|
||||
if (discussionFeedback) {
|
||||
// 从 discuss-002-brief.md 提取需求调整建议
|
||||
// 合并新增/修改/删除需求
|
||||
}
|
||||
|
||||
// === 生成 requirements/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/requirements"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
// Parse CLI output → funcReqs[], nfReqs[]
|
||||
const funcReqs = parseFunctionalRequirements(cliOutput)
|
||||
const nfReqs = parseNonFunctionalRequirements(cliOutput)
|
||||
|
||||
// 写入独立 REQ-*.md 文件(每个功能需求一个文件)
|
||||
funcReqs.forEach(req => {
|
||||
const reqFrontmatter = `---
|
||||
id: REQ-${req.id}
|
||||
title: "${req.title}"
|
||||
priority: ${req.priority}
|
||||
status: draft
|
||||
traces:
|
||||
- product-brief.md
|
||||
---`
|
||||
const reqContent = `${reqFrontmatter}
|
||||
|
||||
# REQ-${req.id}: ${req.title}
|
||||
|
||||
## Description
|
||||
${req.description}
|
||||
|
||||
## User Story
|
||||
${req.user_story}
|
||||
|
||||
## Acceptance Criteria
|
||||
${req.acceptance_criteria.map((ac, i) => `${i+1}. ${ac}`).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/spec/requirements/REQ-${req.id}-${req.slug}.md`, reqContent)
|
||||
})
|
||||
|
||||
// 写入独立 NFR-*.md 文件
|
||||
nfReqs.forEach(nfr => {
|
||||
const nfrFrontmatter = `---
|
||||
id: NFR-${nfr.type}-${nfr.id}
|
||||
type: ${nfr.type}
|
||||
title: "${nfr.title}"
|
||||
status: draft
|
||||
traces:
|
||||
- product-brief.md
|
||||
---`
|
||||
const nfrContent = `${nfrFrontmatter}
|
||||
|
||||
# NFR-${nfr.type}-${nfr.id}: ${nfr.title}
|
||||
|
||||
## Requirement
|
||||
${nfr.requirement}
|
||||
|
||||
## Metric & Target
|
||||
${nfr.metric} — Target: ${nfr.target}
|
||||
`
|
||||
Write(`${sessionFolder}/spec/requirements/NFR-${nfr.type}-${nfr.id}-${nfr.slug}.md`, nfrContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(汇总 + 链接)
|
||||
const indexFrontmatter = `---
|
||||
session_id: ${specConfig?.session_id || 'unknown'}
|
||||
phase: 3
|
||||
document_type: requirements-index
|
||||
status: draft
|
||||
generated_at: ${timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- product-brief.md
|
||||
---`
|
||||
const indexContent = `${indexFrontmatter}
|
||||
|
||||
# Requirements (PRD)
|
||||
|
||||
## Summary
|
||||
Total: ${funcReqs.length} functional + ${nfReqs.length} non-functional requirements
|
||||
|
||||
## Functional Requirements
|
||||
| ID | Title | Priority | Status |
|
||||
|----|-------|----------|--------|
|
||||
${funcReqs.map(r => `| [REQ-${r.id}](REQ-${r.id}-${r.slug}.md) | ${r.title} | ${r.priority} | draft |`).join('\n')}
|
||||
|
||||
## Non-Functional Requirements
|
||||
| ID | Type | Title |
|
||||
|----|------|-------|
|
||||
${nfReqs.map(n => `| [NFR-${n.type}-${n.id}](NFR-${n.type}-${n.id}-${n.slug}.md) | ${n.type} | ${n.title} |`).join('\n')}
|
||||
|
||||
## MoSCoW Summary
|
||||
- **Must**: ${funcReqs.filter(r => r.priority === 'Must').length}
|
||||
- **Should**: ${funcReqs.filter(r => r.priority === 'Should').length}
|
||||
- **Could**: ${funcReqs.filter(r => r.priority === 'Could').length}
|
||||
- **Won't**: ${funcReqs.filter(r => r.priority === "Won't").length}
|
||||
`
|
||||
Write(`${sessionFolder}/spec/requirements/_index.md`, indexContent)
|
||||
outputPath = 'spec/requirements/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-003: Architecture
|
||||
|
||||
两阶段 CLI:Gemini 架构设计 + Codex 架构挑战/审查。
|
||||
|
||||
```javascript
|
||||
if (docType === 'architecture') {
|
||||
// === 阶段1: 架构设计 (Gemini) ===
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Generate technical architecture for the specified requirements.
|
||||
Success: Complete component architecture, tech stack, and ADRs with justified decisions.
|
||||
|
||||
PRODUCT BRIEF (summary):
|
||||
${priorDocs.productBrief?.slice(0, 3000) || ''}
|
||||
|
||||
REQUIREMENTS:
|
||||
${priorDocs.requirementsIndex?.slice(0, 5000) || ''}
|
||||
|
||||
${sharedContext}
|
||||
|
||||
TASK:
|
||||
- Define system architecture style (monolith, microservices, serverless, etc.) with justification
|
||||
- Identify core components and their responsibilities
|
||||
- Create component interaction diagram (Mermaid graph TD format)
|
||||
- Specify technology stack: languages, frameworks, databases, infrastructure
|
||||
- Generate 2-4 Architecture Decision Records (ADRs):
|
||||
- Each ADR: context, decision, 2-3 alternatives with pros/cons, consequences
|
||||
- Focus on: data storage, API design, authentication, key technical choices
|
||||
- Define data model: key entities and relationships (Mermaid erDiagram format)
|
||||
- Identify security architecture: auth, authorization, data protection
|
||||
- List API endpoints (high-level)
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Complete architecture with: style justification, component diagram, tech stack table, ADRs, data model, security controls, API overview
|
||||
CONSTRAINTS: Architecture must support all Must-have requirements. Prefer proven technologies.
|
||||
" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Wait for Gemini result
|
||||
|
||||
// === 阶段2: 架构审查 (Codex) ===
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Critical review of proposed architecture - identify weaknesses and risks.
|
||||
Success: Actionable feedback with specific concerns and improvement suggestions.
|
||||
|
||||
PROPOSED ARCHITECTURE:
|
||||
${geminiArchitectureOutput.slice(0, 5000)}
|
||||
|
||||
REQUIREMENTS CONTEXT:
|
||||
${priorDocs.requirementsIndex?.slice(0, 2000) || ''}
|
||||
|
||||
TASK:
|
||||
- Challenge each ADR: are the alternatives truly the best options?
|
||||
- Identify scalability bottlenecks in the component design
|
||||
- Assess security gaps: authentication, authorization, data protection
|
||||
- Evaluate technology choices: maturity, community support, fit
|
||||
- Check for over-engineering or under-engineering
|
||||
- Verify architecture covers all Must-have requirements
|
||||
- Rate overall architecture quality: 1-5 with justification
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Architecture review with: per-ADR feedback, scalability concerns, security gaps, technology risks, quality rating
|
||||
CONSTRAINTS: Be genuinely critical, not just validating. Focus on actionable improvements.
|
||||
" --tool codex --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Wait for Codex result
|
||||
|
||||
// === 整合讨论反馈 ===
|
||||
if (discussionFeedback) {
|
||||
// 从 discuss-003-requirements.md 提取架构相关反馈
|
||||
// 合并到架构设计中
|
||||
}
|
||||
|
||||
// === 代码库集成映射(条件性) ===
|
||||
let integrationMapping = null
|
||||
if (priorDocs.discoveryContext) {
|
||||
const dc = JSON.parse(priorDocs.discoveryContext)
|
||||
if (dc.relevant_files) {
|
||||
integrationMapping = dc.relevant_files.map(f => ({
|
||||
new_component: '...',
|
||||
existing_module: f.path,
|
||||
integration_type: 'Extend|Replace|New',
|
||||
notes: f.rationale
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// === 生成 architecture/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/architecture"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
const adrs = parseADRs(geminiArchitectureOutput, codexReviewOutput)
|
||||
|
||||
// 写入独立 ADR-*.md 文件
|
||||
adrs.forEach(adr => {
|
||||
const adrFrontmatter = `---
|
||||
id: ADR-${adr.id}
|
||||
title: "${adr.title}"
|
||||
status: draft
|
||||
traces:
|
||||
- ../requirements/_index.md
|
||||
---`
|
||||
const adrContent = `${adrFrontmatter}
|
||||
|
||||
# ADR-${adr.id}: ${adr.title}
|
||||
|
||||
## Context
|
||||
${adr.context}
|
||||
|
||||
## Decision
|
||||
${adr.decision}
|
||||
|
||||
## Alternatives
|
||||
${adr.alternatives.map((alt, i) => `### Option ${i+1}: ${alt.name}\n- **Pros**: ${alt.pros.join(', ')}\n- **Cons**: ${alt.cons.join(', ')}`).join('\n\n')}
|
||||
|
||||
## Consequences
|
||||
${adr.consequences}
|
||||
|
||||
## Review Feedback
|
||||
${adr.reviewFeedback || 'N/A'}
|
||||
`
|
||||
Write(`${sessionFolder}/spec/architecture/ADR-${adr.id}-${adr.slug}.md`, adrContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(含 Mermaid 组件图 + ER图 + 链接)
|
||||
const archIndexFrontmatter = `---
|
||||
session_id: ${specConfig?.session_id || 'unknown'}
|
||||
phase: 4
|
||||
document_type: architecture-index
|
||||
status: draft
|
||||
generated_at: ${timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- ../product-brief.md
|
||||
- ../requirements/_index.md
|
||||
---`
|
||||
// 包含: system overview, component diagram (Mermaid), tech stack table,
|
||||
// ADR links table, data model (Mermaid erDiagram), API design, security controls
|
||||
Write(`${sessionFolder}/spec/architecture/_index.md`, archIndexContent)
|
||||
outputPath = 'spec/architecture/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
#### DRAFT-004: Epics & Stories
|
||||
|
||||
通过 Gemini CLI 分解为 Epic,生成 EPIC-*.md 文件。
|
||||
|
||||
```javascript
|
||||
if (docType === 'epics') {
|
||||
// === Epic 分解 CLI ===
|
||||
Bash({
|
||||
command: `ccw cli -p "PURPOSE: Decompose requirements into executable Epics and Stories for implementation planning.
|
||||
Success: 3-7 Epics with prioritized Stories, dependency map, and MVP subset clearly defined.
|
||||
|
||||
PRODUCT BRIEF (summary):
|
||||
${priorDocs.productBrief?.slice(0, 2000) || ''}
|
||||
|
||||
REQUIREMENTS:
|
||||
${priorDocs.requirementsIndex?.slice(0, 5000) || ''}
|
||||
|
||||
ARCHITECTURE (summary):
|
||||
${priorDocs.architectureIndex?.slice(0, 3000) || ''}
|
||||
|
||||
TASK:
|
||||
- Group requirements into 3-7 logical Epics:
|
||||
- Each Epic: EPIC-NNN ID, title, description, priority (Must/Should/Could)
|
||||
- Group by functional domain or user journey stage
|
||||
- Tag MVP Epics (minimum set for initial release)
|
||||
- For each Epic, generate 2-5 Stories:
|
||||
- Each Story: STORY-{EPIC}-NNN ID, title
|
||||
- User story format: As a [persona], I want [action] so that [benefit]
|
||||
- 2-4 acceptance criteria per story (testable)
|
||||
- Relative size estimate: S/M/L/XL
|
||||
- Trace to source requirement(s): REQ-NNN
|
||||
- Create dependency map:
|
||||
- Cross-Epic dependencies (which Epics block others)
|
||||
- Mermaid graph LR format
|
||||
- Recommended execution order with rationale
|
||||
- Define MVP:
|
||||
- Which Epics are in MVP
|
||||
- MVP definition of done (3-5 criteria)
|
||||
- What is explicitly deferred post-MVP
|
||||
|
||||
MODE: analysis
|
||||
EXPECTED: Structured output with: Epic list (ID, title, priority, MVP flag), Stories per Epic (ID, user story, AC, size, trace), dependency Mermaid diagram, execution order, MVP definition
|
||||
CONSTRAINTS: Every Must-have requirement must appear in at least one Story. Stories must be small enough to implement independently. Dependencies should be minimized across Epics.
|
||||
" --tool gemini --mode analysis`,
|
||||
run_in_background: true
|
||||
})
|
||||
|
||||
// Wait for CLI result
|
||||
|
||||
// === 整合讨论反馈 ===
|
||||
if (discussionFeedback) {
|
||||
// 从 discuss-004-architecture.md 提取执行相关反馈
|
||||
// 调整 Epic 粒度、MVP 范围
|
||||
}
|
||||
|
||||
// === 生成 epics/ 目录 ===
|
||||
Bash(`mkdir -p "${sessionFolder}/spec/epics"`)
|
||||
|
||||
const timestamp = new Date().toISOString()
|
||||
const epicsList = parseEpics(cliOutput)
|
||||
|
||||
// 写入独立 EPIC-*.md 文件(含 stories)
|
||||
epicsList.forEach(epic => {
|
||||
const epicFrontmatter = `---
|
||||
id: EPIC-${epic.id}
|
||||
title: "${epic.title}"
|
||||
priority: ${epic.priority}
|
||||
mvp: ${epic.mvp}
|
||||
size: ${epic.size}
|
||||
requirements:
|
||||
${epic.reqs.map(r => ` - ${r}`).join('\n')}
|
||||
architecture:
|
||||
${epic.adrs.map(a => ` - ${a}`).join('\n')}
|
||||
dependencies:
|
||||
${epic.deps.map(d => ` - ${d}`).join('\n')}
|
||||
status: draft
|
||||
---`
|
||||
const storiesContent = epic.stories.map(s => `### ${s.id}: ${s.title}
|
||||
|
||||
**User Story**: ${s.user_story}
|
||||
**Size**: ${s.size}
|
||||
**Traces**: ${s.traces.join(', ')}
|
||||
|
||||
**Acceptance Criteria**:
|
||||
${s.acceptance_criteria.map((ac, i) => `${i+1}. ${ac}`).join('\n')}
|
||||
`).join('\n')
|
||||
|
||||
const epicContent = `${epicFrontmatter}
|
||||
|
||||
# EPIC-${epic.id}: ${epic.title}
|
||||
|
||||
## Description
|
||||
${epic.description}
|
||||
|
||||
## Stories
|
||||
${storiesContent}
|
||||
|
||||
## Requirements
|
||||
${epic.reqs.map(r => `- [${r}](../requirements/${r}.md)`).join('\n')}
|
||||
|
||||
## Architecture
|
||||
${epic.adrs.map(a => `- [${a}](../architecture/${a}.md)`).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/spec/epics/EPIC-${epic.id}-${epic.slug}.md`, epicContent)
|
||||
})
|
||||
|
||||
// 写入 _index.md(含 Mermaid 依赖图 + MVP + 链接)
|
||||
const epicsIndexFrontmatter = `---
|
||||
session_id: ${specConfig?.session_id || 'unknown'}
|
||||
phase: 5
|
||||
document_type: epics-index
|
||||
status: draft
|
||||
generated_at: ${timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- ../requirements/_index.md
|
||||
- ../architecture/_index.md
|
||||
---`
|
||||
// 包含: Epic overview table (with links), dependency Mermaid diagram,
|
||||
// execution order, MVP scope, traceability matrix
|
||||
Write(`${sessionFolder}/spec/epics/_index.md`, epicsIndexContent)
|
||||
outputPath = 'spec/epics/_index.md'
|
||||
}
|
||||
```
|
||||
|
||||
### 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 |
|
||||
@@ -1,192 +0,0 @@
|
||||
# Document Standards
|
||||
|
||||
Defines format conventions, YAML frontmatter schema, naming rules, and content structure for all spec-generator outputs.
|
||||
|
||||
## When to Use
|
||||
|
||||
| Phase | Usage | Section |
|
||||
|-------|-------|---------|
|
||||
| All Phases | Frontmatter format | YAML Frontmatter Schema |
|
||||
| All Phases | File naming | Naming Conventions |
|
||||
| Phase 2-5 | Document structure | Content Structure |
|
||||
| Phase 6 | Validation reference | All sections |
|
||||
|
||||
---
|
||||
|
||||
## YAML Frontmatter Schema
|
||||
|
||||
Every generated document MUST begin with YAML frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
session_id: SPEC-{slug}-{YYYY-MM-DD}
|
||||
phase: {1-6}
|
||||
document_type: {product-brief|requirements|architecture|epics|readiness-report|spec-summary}
|
||||
status: draft|review|complete
|
||||
generated_at: {ISO8601 timestamp}
|
||||
stepsCompleted: []
|
||||
version: 1
|
||||
dependencies:
|
||||
- {list of input documents used}
|
||||
---
|
||||
```
|
||||
|
||||
### Field Definitions
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `session_id` | string | Yes | Session identifier matching spec-config.json |
|
||||
| `phase` | number | Yes | Phase number that generated this document (1-6) |
|
||||
| `document_type` | string | Yes | One of: product-brief, requirements, architecture, epics, readiness-report, spec-summary |
|
||||
| `status` | enum | Yes | draft (initial), review (user reviewed), complete (finalized) |
|
||||
| `generated_at` | string | Yes | ISO8601 timestamp of generation |
|
||||
| `stepsCompleted` | array | Yes | List of step IDs completed during generation |
|
||||
| `version` | number | Yes | Document version, incremented on re-generation |
|
||||
| `dependencies` | array | No | List of input files this document depends on |
|
||||
|
||||
### Status Transitions
|
||||
|
||||
```
|
||||
draft -> review -> complete
|
||||
| ^
|
||||
+-------------------+ (direct promotion in auto mode)
|
||||
```
|
||||
|
||||
- **draft**: Initial generation, not yet user-reviewed
|
||||
- **review**: User has reviewed and provided feedback
|
||||
- **complete**: Finalized, ready for downstream consumption
|
||||
|
||||
In auto mode (`-y`), documents are promoted directly from `draft` to `complete`.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Session ID Format
|
||||
|
||||
```
|
||||
SPEC-{slug}-{YYYY-MM-DD}
|
||||
```
|
||||
|
||||
- **slug**: Lowercase, alphanumeric + Chinese characters, hyphens as separators, max 40 chars
|
||||
- **date**: UTC+8 date in YYYY-MM-DD format
|
||||
|
||||
Examples:
|
||||
- `SPEC-task-management-system-2026-02-11`
|
||||
- `SPEC-user-auth-oauth-2026-02-11`
|
||||
|
||||
### Output Files
|
||||
|
||||
| File | Phase | Description |
|
||||
|------|-------|-------------|
|
||||
| `spec-config.json` | 1 | Session configuration and state |
|
||||
| `discovery-context.json` | 1 | Codebase exploration results (optional) |
|
||||
| `product-brief.md` | 2 | Product brief document |
|
||||
| `requirements.md` | 3 | PRD document |
|
||||
| `architecture.md` | 4 | Architecture decisions document |
|
||||
| `epics.md` | 5 | Epic/Story breakdown document |
|
||||
| `readiness-report.md` | 6 | Quality validation report |
|
||||
| `spec-summary.md` | 6 | One-page executive summary |
|
||||
|
||||
### Output Directory
|
||||
|
||||
```
|
||||
.workflow/.spec/{session-id}/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Content Structure
|
||||
|
||||
### Heading Hierarchy
|
||||
|
||||
- `#` (H1): Document title only (one per document)
|
||||
- `##` (H2): Major sections
|
||||
- `###` (H3): Subsections
|
||||
- `####` (H4): Detail items (use sparingly)
|
||||
|
||||
Maximum depth: 4 levels. Prefer flat structures.
|
||||
|
||||
### Section Ordering
|
||||
|
||||
Every document follows this general pattern:
|
||||
|
||||
1. **YAML Frontmatter** (mandatory)
|
||||
2. **Title** (H1)
|
||||
3. **Executive Summary** (2-3 sentences)
|
||||
4. **Core Content Sections** (H2, document-specific)
|
||||
5. **Open Questions / Risks** (if applicable)
|
||||
6. **References / Traceability** (links to upstream/downstream docs)
|
||||
|
||||
### Formatting Rules
|
||||
|
||||
| Element | Format | Example |
|
||||
|---------|--------|---------|
|
||||
| Requirements | `REQ-{NNN}` prefix | REQ-001: User login |
|
||||
| Acceptance criteria | Checkbox list | `- [ ] User can log in with email` |
|
||||
| Architecture decisions | `ADR-{NNN}` prefix | ADR-001: Use PostgreSQL |
|
||||
| Epics | `EPIC-{NNN}` prefix | EPIC-001: Authentication |
|
||||
| Stories | `STORY-{EPIC}-{NNN}` prefix | STORY-001-001: Login form |
|
||||
| Priority tags | MoSCoW labels | `[Must]`, `[Should]`, `[Could]`, `[Won't]` |
|
||||
| Mermaid diagrams | Fenced code blocks | ````mermaid ... ``` `` |
|
||||
| Code examples | Language-tagged blocks | ````typescript ... ``` `` |
|
||||
|
||||
### Cross-Reference Format
|
||||
|
||||
Use relative references between documents:
|
||||
|
||||
```markdown
|
||||
See [Product Brief](product-brief.md#section-name) for details.
|
||||
Derived from [REQ-001](requirements.md#req-001).
|
||||
```
|
||||
|
||||
### Language
|
||||
|
||||
- Document body: Follow user's input language (Chinese or English)
|
||||
- Technical identifiers: Always English (REQ-001, ADR-001, EPIC-001)
|
||||
- YAML frontmatter keys: Always English
|
||||
|
||||
---
|
||||
|
||||
## spec-config.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "string (required)",
|
||||
"seed_input": "string (required) - original user input",
|
||||
"input_type": "text|file (required)",
|
||||
"timestamp": "ISO8601 (required)",
|
||||
"mode": "interactive|auto (required)",
|
||||
"complexity": "simple|moderate|complex (required)",
|
||||
"depth": "light|standard|comprehensive (required)",
|
||||
"focus_areas": ["string array"],
|
||||
"seed_analysis": {
|
||||
"problem_statement": "string",
|
||||
"target_users": ["string array"],
|
||||
"domain": "string",
|
||||
"constraints": ["string array"],
|
||||
"dimensions": ["string array - 3-5 exploration dimensions"]
|
||||
},
|
||||
"has_codebase": "boolean",
|
||||
"phasesCompleted": [
|
||||
{
|
||||
"phase": "number (1-6)",
|
||||
"name": "string (phase name)",
|
||||
"output_file": "string (primary output file)",
|
||||
"completed_at": "ISO8601"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Every document starts with valid YAML frontmatter
|
||||
- [ ] `session_id` matches across all documents in a session
|
||||
- [ ] `status` field reflects current document state
|
||||
- [ ] All cross-references resolve to valid targets
|
||||
- [ ] Heading hierarchy is correct (no skipped levels)
|
||||
- [ ] Technical identifiers use correct prefixes
|
||||
- [ ] Output files are in the correct directory
|
||||
@@ -1,207 +0,0 @@
|
||||
# Quality Gates
|
||||
|
||||
Per-phase quality gate criteria and scoring dimensions for spec-generator outputs.
|
||||
|
||||
## When to Use
|
||||
|
||||
| Phase | Usage | Section |
|
||||
|-------|-------|---------|
|
||||
| Phase 2-5 | Post-generation self-check | Per-Phase Gates |
|
||||
| Phase 6 | Cross-document validation | Cross-Document Validation |
|
||||
| Phase 6 | Final scoring | Scoring Dimensions |
|
||||
|
||||
---
|
||||
|
||||
## Quality Thresholds
|
||||
|
||||
| Gate | Score | Action |
|
||||
|------|-------|--------|
|
||||
| **Pass** | >= 80% | Continue to next phase |
|
||||
| **Review** | 60-79% | Log warnings, continue with caveats |
|
||||
| **Fail** | < 60% | Must address issues before continuing |
|
||||
|
||||
In auto mode (`-y`), Review-level issues are logged but do not block progress.
|
||||
|
||||
---
|
||||
|
||||
## Scoring Dimensions
|
||||
|
||||
### 1. Completeness (25%)
|
||||
|
||||
All required sections present with substantive content.
|
||||
|
||||
| Score | Criteria |
|
||||
|-------|----------|
|
||||
| 100% | All template sections filled with detailed content |
|
||||
| 75% | All sections present, some lack detail |
|
||||
| 50% | Major sections present but minor sections missing |
|
||||
| 25% | Multiple major sections missing or empty |
|
||||
| 0% | Document is a skeleton only |
|
||||
|
||||
### 2. Consistency (25%)
|
||||
|
||||
Terminology, formatting, and references are uniform across documents.
|
||||
|
||||
| Score | Criteria |
|
||||
|-------|----------|
|
||||
| 100% | All terms consistent, all references valid, formatting uniform |
|
||||
| 75% | Minor terminology variations, all references valid |
|
||||
| 50% | Some inconsistent terms, 1-2 broken references |
|
||||
| 25% | Frequent inconsistencies, multiple broken references |
|
||||
| 0% | Documents contradict each other |
|
||||
|
||||
### 3. Traceability (25%)
|
||||
|
||||
Requirements, architecture decisions, and stories trace back to goals.
|
||||
|
||||
| Score | Criteria |
|
||||
|-------|----------|
|
||||
| 100% | Every story traces to a requirement, every requirement traces to a goal |
|
||||
| 75% | Most items traceable, few orphans |
|
||||
| 50% | Partial traceability, some disconnected items |
|
||||
| 25% | Weak traceability, many orphan items |
|
||||
| 0% | No traceability between documents |
|
||||
|
||||
### 4. Depth (25%)
|
||||
|
||||
Content provides sufficient detail for execution teams.
|
||||
|
||||
| Score | Criteria |
|
||||
|-------|----------|
|
||||
| 100% | Acceptance criteria specific and testable, architecture decisions justified, stories estimable |
|
||||
| 75% | Most items detailed enough, few vague areas |
|
||||
| 50% | Mix of detailed and vague content |
|
||||
| 25% | Mostly high-level, lacking actionable detail |
|
||||
| 0% | Too abstract for execution |
|
||||
|
||||
---
|
||||
|
||||
## Per-Phase Quality Gates
|
||||
|
||||
### Phase 1: Discovery
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| Session ID valid | Matches `SPEC-{slug}-{date}` format | Error |
|
||||
| Problem statement exists | Non-empty, >= 20 characters | Error |
|
||||
| Target users identified | >= 1 user group | Error |
|
||||
| Dimensions generated | 3-5 exploration dimensions | Warning |
|
||||
| Constraints listed | >= 0 (can be empty with justification) | Info |
|
||||
|
||||
### Phase 2: Product Brief
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| Vision statement | Clear, 1-3 sentences | Error |
|
||||
| Problem statement | Specific and measurable | Error |
|
||||
| Target users | >= 1 persona with needs described | Error |
|
||||
| Goals defined | >= 2 measurable goals | Error |
|
||||
| Success metrics | >= 2 quantifiable metrics | Warning |
|
||||
| Scope boundaries | In-scope and out-of-scope listed | Warning |
|
||||
| Multi-perspective | >= 2 CLI perspectives synthesized | Info |
|
||||
|
||||
### Phase 3: Requirements (PRD)
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| Functional requirements | >= 3 with REQ-NNN IDs | Error |
|
||||
| Acceptance criteria | Every requirement has >= 1 criterion | Error |
|
||||
| MoSCoW priority | Every requirement tagged | Error |
|
||||
| Non-functional requirements | >= 1 (performance, security, etc.) | Warning |
|
||||
| User stories | >= 1 per Must-have requirement | Warning |
|
||||
| Traceability | Requirements trace to product brief goals | Warning |
|
||||
|
||||
### Phase 4: Architecture
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| Component diagram | Present (Mermaid or ASCII) | Error |
|
||||
| Tech stack specified | Languages, frameworks, key libraries | Error |
|
||||
| ADR present | >= 1 Architecture Decision Record | Error |
|
||||
| ADR has alternatives | Each ADR lists >= 2 options considered | Warning |
|
||||
| Integration points | External systems/APIs identified | Warning |
|
||||
| Data model | Key entities and relationships described | Warning |
|
||||
| Codebase mapping | Mapped to existing code (if has_codebase) | Info |
|
||||
|
||||
### Phase 5: Epics & Stories
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| Epics defined | 3-7 epics with EPIC-NNN IDs | Error |
|
||||
| MVP subset | >= 1 epic tagged as MVP | Error |
|
||||
| Stories per epic | 2-5 stories per epic | Error |
|
||||
| Story format | "As a...I want...So that..." pattern | Warning |
|
||||
| Dependency map | Cross-epic dependencies documented | Warning |
|
||||
| Estimation hints | Relative sizing (S/M/L/XL) per story | Info |
|
||||
| Traceability | Stories trace to requirements | Warning |
|
||||
|
||||
### Phase 6: Readiness Check
|
||||
|
||||
| Check | Criteria | Severity |
|
||||
|-------|----------|----------|
|
||||
| All documents exist | product-brief, requirements, architecture, epics | Error |
|
||||
| Frontmatter valid | All YAML frontmatter parseable and correct | Error |
|
||||
| Cross-references valid | All document links resolve | Error |
|
||||
| Overall score >= 60% | Weighted average across 4 dimensions | Error |
|
||||
| No unresolved Errors | All Error-severity issues addressed | Error |
|
||||
| Summary generated | spec-summary.md created | Warning |
|
||||
|
||||
---
|
||||
|
||||
## Cross-Document Validation
|
||||
|
||||
Checks performed during Phase 6 across all documents:
|
||||
|
||||
### Completeness Matrix
|
||||
|
||||
```
|
||||
Product Brief goals -> Requirements (each goal has >= 1 requirement)
|
||||
Requirements -> Architecture (each Must requirement has design coverage)
|
||||
Requirements -> Epics (each Must requirement appears in >= 1 story)
|
||||
Architecture ADRs -> Epics (tech choices reflected in implementation stories)
|
||||
```
|
||||
|
||||
### Consistency Checks
|
||||
|
||||
| Check | Documents | Rule |
|
||||
|-------|-----------|------|
|
||||
| Terminology | All | Same term used consistently (no synonyms for same concept) |
|
||||
| User personas | Brief + PRD + Epics | Same user names/roles throughout |
|
||||
| Scope | Brief + PRD | PRD scope does not exceed brief scope |
|
||||
| Tech stack | Architecture + Epics | Stories reference correct technologies |
|
||||
|
||||
### Traceability Matrix Format
|
||||
|
||||
```markdown
|
||||
| Goal | Requirements | Architecture | Epics |
|
||||
|------|-------------|--------------|-------|
|
||||
| G-001: ... | REQ-001, REQ-002 | ADR-001 | EPIC-001 |
|
||||
| G-002: ... | REQ-003 | ADR-002 | EPIC-002, EPIC-003 |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue Classification
|
||||
|
||||
### Error (Must Fix)
|
||||
|
||||
- Missing required document or section
|
||||
- Broken cross-references
|
||||
- Contradictory information between documents
|
||||
- Empty acceptance criteria on Must-have requirements
|
||||
- No MVP subset defined in epics
|
||||
|
||||
### Warning (Should Fix)
|
||||
|
||||
- Vague acceptance criteria
|
||||
- Missing non-functional requirements
|
||||
- No success metrics defined
|
||||
- Incomplete traceability
|
||||
- Missing architecture review notes
|
||||
|
||||
### Info (Nice to Have)
|
||||
|
||||
- Could add more detailed personas
|
||||
- Consider additional ADR alternatives
|
||||
- Story estimation hints missing
|
||||
- Mermaid diagrams could be more detailed
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"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": {
|
||||
"base": ".workflow/.team/TLS-{slug}-{YYYY-MM-DD}/",
|
||||
"spec": "spec/",
|
||||
"discussions": "discussions/",
|
||||
"plan": "plan/",
|
||||
"messages": ".workflow/.team-msg/{team-name}/"
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
# Architecture Document Template (Directory Structure)
|
||||
|
||||
Template for generating architecture decision documents as a directory of individual ADR files in Phase 4.
|
||||
|
||||
## Usage Context
|
||||
|
||||
| Phase | Usage |
|
||||
|-------|-------|
|
||||
| Phase 4 (Architecture) | Generate `architecture/` directory from requirements analysis |
|
||||
| Output Location | `{workDir}/architecture/` |
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
{workDir}/architecture/
|
||||
├── _index.md # Overview, components, tech stack, data model, security
|
||||
├── ADR-001-{slug}.md # Individual Architecture Decision Record
|
||||
├── ADR-002-{slug}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: _index.md
|
||||
|
||||
```markdown
|
||||
---
|
||||
session_id: {session_id}
|
||||
phase: 4
|
||||
document_type: architecture-index
|
||||
status: draft
|
||||
generated_at: {timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- ../spec-config.json
|
||||
- ../product-brief.md
|
||||
- ../requirements/_index.md
|
||||
---
|
||||
|
||||
# Architecture: {product_name}
|
||||
|
||||
{executive_summary - high-level architecture approach and key decisions}
|
||||
|
||||
## System Overview
|
||||
|
||||
### Architecture Style
|
||||
{description of chosen architecture style: microservices, monolith, serverless, etc.}
|
||||
|
||||
### System Context Diagram
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context Diagram
|
||||
Person(user, "User", "Primary user")
|
||||
System(system, "{product_name}", "Core system")
|
||||
System_Ext(ext1, "{external_system}", "{description}")
|
||||
Rel(user, system, "Uses")
|
||||
Rel(system, ext1, "Integrates with")
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### Component Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "{product_name}"
|
||||
A[Component A] --> B[Component B]
|
||||
B --> C[Component C]
|
||||
A --> D[Component D]
|
||||
end
|
||||
B --> E[External Service]
|
||||
```
|
||||
|
||||
### Component Descriptions
|
||||
|
||||
| Component | Responsibility | Technology | Dependencies |
|
||||
|-----------|---------------|------------|--------------|
|
||||
| {component_name} | {what it does} | {tech stack} | {depends on} |
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
|
||||
| Layer | Technology | Version | Rationale |
|
||||
|-------|-----------|---------|-----------|
|
||||
| Frontend | {technology} | {version} | {why chosen} |
|
||||
| Backend | {technology} | {version} | {why chosen} |
|
||||
| Database | {technology} | {version} | {why chosen} |
|
||||
| Infrastructure | {technology} | {version} | {why chosen} |
|
||||
|
||||
### Key Libraries & Frameworks
|
||||
|
||||
| Library | Purpose | License |
|
||||
|---------|---------|---------|
|
||||
| {library_name} | {purpose} | {license} |
|
||||
|
||||
## Architecture Decision Records
|
||||
|
||||
| ADR | Title | Status | Key Choice |
|
||||
|-----|-------|--------|------------|
|
||||
| [ADR-001](ADR-001-{slug}.md) | {title} | Accepted | {one-line summary} |
|
||||
| [ADR-002](ADR-002-{slug}.md) | {title} | Accepted | {one-line summary} |
|
||||
| [ADR-003](ADR-003-{slug}.md) | {title} | Proposed | {one-line summary} |
|
||||
|
||||
## Data Architecture
|
||||
|
||||
### Data Model
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
ENTITY_A ||--o{ ENTITY_B : "has many"
|
||||
ENTITY_A {
|
||||
string id PK
|
||||
string name
|
||||
datetime created_at
|
||||
}
|
||||
ENTITY_B {
|
||||
string id PK
|
||||
string entity_a_id FK
|
||||
string value
|
||||
}
|
||||
```
|
||||
|
||||
### Data Storage Strategy
|
||||
|
||||
| Data Type | Storage | Retention | Backup |
|
||||
|-----------|---------|-----------|--------|
|
||||
| {type} | {storage solution} | {retention policy} | {backup strategy} |
|
||||
|
||||
## API Design
|
||||
|
||||
### API Overview
|
||||
|
||||
| Endpoint | Method | Purpose | Auth |
|
||||
|----------|--------|---------|------|
|
||||
| {/api/resource} | {GET/POST/etc} | {purpose} | {auth type} |
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Security Controls
|
||||
|
||||
| Control | Implementation | Requirement |
|
||||
|---------|---------------|-------------|
|
||||
| Authentication | {approach} | [NFR-S-{NNN}](../requirements/NFR-S-{NNN}-{slug}.md) |
|
||||
| Authorization | {approach} | [NFR-S-{NNN}](../requirements/NFR-S-{NNN}-{slug}.md) |
|
||||
| Data Protection | {approach} | [NFR-S-{NNN}](../requirements/NFR-S-{NNN}-{slug}.md) |
|
||||
|
||||
## Infrastructure & Deployment
|
||||
|
||||
### Deployment Architecture
|
||||
|
||||
{description of deployment model: containers, serverless, VMs, etc.}
|
||||
|
||||
### Environment Strategy
|
||||
|
||||
| Environment | Purpose | Configuration |
|
||||
|-------------|---------|---------------|
|
||||
| Development | Local development | {config} |
|
||||
| Staging | Pre-production testing | {config} |
|
||||
| Production | Live system | {config} |
|
||||
|
||||
## Codebase Integration
|
||||
|
||||
{if has_codebase is true:}
|
||||
|
||||
### Existing Code Mapping
|
||||
|
||||
| New Component | Existing Module | Integration Type | Notes |
|
||||
|--------------|----------------|------------------|-------|
|
||||
| {component} | {existing module path} | Extend/Replace/New | {notes} |
|
||||
|
||||
### Migration Notes
|
||||
{any migration considerations for existing code}
|
||||
|
||||
## Quality Attributes
|
||||
|
||||
| Attribute | Target | Measurement | ADR Reference |
|
||||
|-----------|--------|-------------|---------------|
|
||||
| Performance | {target} | {how measured} | [ADR-{NNN}](ADR-{NNN}-{slug}.md) |
|
||||
| Scalability | {target} | {how measured} | [ADR-{NNN}](ADR-{NNN}-{slug}.md) |
|
||||
| Reliability | {target} | {how measured} | [ADR-{NNN}](ADR-{NNN}-{slug}.md) |
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Impact | Probability | Mitigation |
|
||||
|------|--------|-------------|------------|
|
||||
| {risk} | High/Medium/Low | High/Medium/Low | {mitigation approach} |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] {architectural question 1}
|
||||
- [ ] {architectural question 2}
|
||||
|
||||
## References
|
||||
|
||||
- Derived from: [Requirements](../requirements/_index.md), [Product Brief](../product-brief.md)
|
||||
- Next: [Epics & Stories](../epics/_index.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: ADR-NNN-{slug}.md (Individual Architecture Decision Record)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: ADR-{NNN}
|
||||
status: Accepted
|
||||
traces_to: [{REQ-NNN}, {NFR-X-NNN}]
|
||||
date: {timestamp}
|
||||
---
|
||||
|
||||
# ADR-{NNN}: {decision_title}
|
||||
|
||||
## Context
|
||||
|
||||
{what is the situation that motivates this decision}
|
||||
|
||||
## Decision
|
||||
|
||||
{what is the chosen approach}
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
| Option | Pros | Cons |
|
||||
|--------|------|------|
|
||||
| {option_1 - chosen} | {pros} | {cons} |
|
||||
| {option_2} | {pros} | {cons} |
|
||||
| {option_3} | {pros} | {cons} |
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive**: {positive outcomes}
|
||||
- **Negative**: {tradeoffs accepted}
|
||||
- **Risks**: {risks to monitor}
|
||||
|
||||
## Traces
|
||||
|
||||
- **Requirements**: [REQ-{NNN}](../requirements/REQ-{NNN}-{slug}.md), [NFR-X-{NNN}](../requirements/NFR-X-{NNN}-{slug}.md)
|
||||
- **Implemented by**: [EPIC-{NNN}](../epics/EPIC-{NNN}-{slug}.md) (added in Phase 5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variable Descriptions
|
||||
|
||||
| Variable | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| `{session_id}` | spec-config.json | Session identifier |
|
||||
| `{timestamp}` | Runtime | ISO8601 generation timestamp |
|
||||
| `{product_name}` | product-brief.md | Product/feature name |
|
||||
| `{NNN}` | Auto-increment | ADR/requirement number |
|
||||
| `{slug}` | Auto-generated | Kebab-case from decision title |
|
||||
| `{has_codebase}` | spec-config.json | Whether existing codebase exists |
|
||||
@@ -1,196 +0,0 @@
|
||||
# Epics & Stories Template (Directory Structure)
|
||||
|
||||
Template for generating epic/story breakdown as a directory of individual Epic files in Phase 5.
|
||||
|
||||
## Usage Context
|
||||
|
||||
| Phase | Usage |
|
||||
|-------|-------|
|
||||
| Phase 5 (Epics & Stories) | Generate `epics/` directory from requirements decomposition |
|
||||
| Output Location | `{workDir}/epics/` |
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
{workDir}/epics/
|
||||
├── _index.md # Overview table + dependency map + MVP scope + execution order
|
||||
├── EPIC-001-{slug}.md # Individual Epic with its Stories
|
||||
├── EPIC-002-{slug}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: _index.md
|
||||
|
||||
```markdown
|
||||
---
|
||||
session_id: {session_id}
|
||||
phase: 5
|
||||
document_type: epics-index
|
||||
status: draft
|
||||
generated_at: {timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- ../spec-config.json
|
||||
- ../product-brief.md
|
||||
- ../requirements/_index.md
|
||||
- ../architecture/_index.md
|
||||
---
|
||||
|
||||
# Epics & Stories: {product_name}
|
||||
|
||||
{executive_summary - overview of epic structure and MVP scope}
|
||||
|
||||
## Epic Overview
|
||||
|
||||
| Epic ID | Title | Priority | MVP | Stories | Est. Size |
|
||||
|---------|-------|----------|-----|---------|-----------|
|
||||
| [EPIC-001](EPIC-001-{slug}.md) | {title} | Must | Yes | {n} | {S/M/L/XL} |
|
||||
| [EPIC-002](EPIC-002-{slug}.md) | {title} | Must | Yes | {n} | {S/M/L/XL} |
|
||||
| [EPIC-003](EPIC-003-{slug}.md) | {title} | Should | No | {n} | {S/M/L/XL} |
|
||||
|
||||
## Dependency Map
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
EPIC-001 --> EPIC-002
|
||||
EPIC-001 --> EPIC-003
|
||||
EPIC-002 --> EPIC-004
|
||||
EPIC-003 --> EPIC-005
|
||||
```
|
||||
|
||||
### Dependency Notes
|
||||
{explanation of why these dependencies exist and suggested execution order}
|
||||
|
||||
### Recommended Execution Order
|
||||
1. [EPIC-{NNN}](EPIC-{NNN}-{slug}.md): {reason - foundational}
|
||||
2. [EPIC-{NNN}](EPIC-{NNN}-{slug}.md): {reason - depends on #1}
|
||||
3. ...
|
||||
|
||||
## MVP Scope
|
||||
|
||||
### MVP Epics
|
||||
{list of epics included in MVP with justification, linking to each}
|
||||
|
||||
### MVP Definition of Done
|
||||
- [ ] {MVP completion criterion 1}
|
||||
- [ ] {MVP completion criterion 2}
|
||||
- [ ] {MVP completion criterion 3}
|
||||
|
||||
## Traceability Matrix
|
||||
|
||||
| Requirement | Epic | Stories | Architecture |
|
||||
|-------------|------|---------|--------------|
|
||||
| [REQ-001](../requirements/REQ-001-{slug}.md) | [EPIC-001](EPIC-001-{slug}.md) | STORY-001-001, STORY-001-002 | [ADR-001](../architecture/ADR-001-{slug}.md) |
|
||||
| [REQ-002](../requirements/REQ-002-{slug}.md) | [EPIC-001](EPIC-001-{slug}.md) | STORY-001-003 | Component B |
|
||||
| [REQ-003](../requirements/REQ-003-{slug}.md) | [EPIC-002](EPIC-002-{slug}.md) | STORY-002-001 | [ADR-002](../architecture/ADR-002-{slug}.md) |
|
||||
|
||||
## Estimation Summary
|
||||
|
||||
| Size | Meaning | Count |
|
||||
|------|---------|-------|
|
||||
| S | Small - well-understood, minimal risk | {n} |
|
||||
| M | Medium - some complexity, moderate risk | {n} |
|
||||
| L | Large - significant complexity, should consider splitting | {n} |
|
||||
| XL | Extra Large - high complexity, must split before implementation | {n} |
|
||||
|
||||
## Risks & Considerations
|
||||
|
||||
| Risk | Affected Epics | Mitigation |
|
||||
|------|---------------|------------|
|
||||
| {risk description} | [EPIC-{NNN}](EPIC-{NNN}-{slug}.md) | {mitigation} |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] {question about scope or implementation 1}
|
||||
- [ ] {question about scope or implementation 2}
|
||||
|
||||
## References
|
||||
|
||||
- Derived from: [Requirements](../requirements/_index.md), [Architecture](../architecture/_index.md)
|
||||
- Handoff to: execution workflows (lite-plan, plan, req-plan)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: EPIC-NNN-{slug}.md (Individual Epic)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: EPIC-{NNN}
|
||||
priority: {Must|Should|Could}
|
||||
mvp: {true|false}
|
||||
size: {S|M|L|XL}
|
||||
requirements: [REQ-{NNN}]
|
||||
architecture: [ADR-{NNN}]
|
||||
dependencies: [EPIC-{NNN}]
|
||||
status: draft
|
||||
---
|
||||
|
||||
# EPIC-{NNN}: {epic_title}
|
||||
|
||||
**Priority**: {Must|Should|Could}
|
||||
**MVP**: {Yes|No}
|
||||
**Estimated Size**: {S|M|L|XL}
|
||||
|
||||
## Description
|
||||
|
||||
{detailed epic description}
|
||||
|
||||
## Requirements
|
||||
|
||||
- [REQ-{NNN}](../requirements/REQ-{NNN}-{slug}.md): {title}
|
||||
- [REQ-{NNN}](../requirements/REQ-{NNN}-{slug}.md): {title}
|
||||
|
||||
## Architecture
|
||||
|
||||
- [ADR-{NNN}](../architecture/ADR-{NNN}-{slug}.md): {title}
|
||||
- Component: {component_name}
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [EPIC-{NNN}](EPIC-{NNN}-{slug}.md) (blocking): {reason}
|
||||
- [EPIC-{NNN}](EPIC-{NNN}-{slug}.md) (soft): {reason}
|
||||
|
||||
## Stories
|
||||
|
||||
### STORY-{EPIC}-001: {story_title}
|
||||
|
||||
**User Story**: As a {persona}, I want to {action} so that {benefit}.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] {criterion 1}
|
||||
- [ ] {criterion 2}
|
||||
- [ ] {criterion 3}
|
||||
|
||||
**Size**: {S|M|L|XL}
|
||||
**Traces to**: [REQ-{NNN}](../requirements/REQ-{NNN}-{slug}.md)
|
||||
|
||||
---
|
||||
|
||||
### STORY-{EPIC}-002: {story_title}
|
||||
|
||||
**User Story**: As a {persona}, I want to {action} so that {benefit}.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] {criterion 1}
|
||||
- [ ] {criterion 2}
|
||||
|
||||
**Size**: {S|M|L|XL}
|
||||
**Traces to**: [REQ-{NNN}](../requirements/REQ-{NNN}-{slug}.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variable Descriptions
|
||||
|
||||
| Variable | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| `{session_id}` | spec-config.json | Session identifier |
|
||||
| `{timestamp}` | Runtime | ISO8601 generation timestamp |
|
||||
| `{product_name}` | product-brief.md | Product/feature name |
|
||||
| `{EPIC}` | Auto-increment | Epic number (3 digits) |
|
||||
| `{NNN}` | Auto-increment | Story/requirement number |
|
||||
| `{slug}` | Auto-generated | Kebab-case from epic/story title |
|
||||
| `{S\|M\|L\|XL}` | CLI analysis | Relative size estimate |
|
||||
@@ -1,133 +0,0 @@
|
||||
# Product Brief Template
|
||||
|
||||
Template for generating product brief documents in Phase 2.
|
||||
|
||||
## Usage Context
|
||||
|
||||
| Phase | Usage |
|
||||
|-------|-------|
|
||||
| Phase 2 (Product Brief) | Generate product-brief.md from multi-CLI analysis |
|
||||
| Output Location | `{workDir}/product-brief.md` |
|
||||
|
||||
---
|
||||
|
||||
## Template
|
||||
|
||||
```markdown
|
||||
---
|
||||
session_id: {session_id}
|
||||
phase: 2
|
||||
document_type: product-brief
|
||||
status: draft
|
||||
generated_at: {timestamp}
|
||||
stepsCompleted: []
|
||||
version: 1
|
||||
dependencies:
|
||||
- spec-config.json
|
||||
---
|
||||
|
||||
# Product Brief: {product_name}
|
||||
|
||||
{executive_summary - 2-3 sentences capturing the essence of the product/feature}
|
||||
|
||||
## Vision
|
||||
|
||||
{vision_statement - clear, aspirational 1-3 sentence statement of what success looks like}
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Situation
|
||||
{description of the current state and pain points}
|
||||
|
||||
### Impact
|
||||
{quantified impact of the problem - who is affected, how much, how often}
|
||||
|
||||
## Target Users
|
||||
|
||||
{for each user persona:}
|
||||
|
||||
### {Persona Name}
|
||||
- **Role**: {user's role/context}
|
||||
- **Needs**: {primary needs related to this product}
|
||||
- **Pain Points**: {current frustrations}
|
||||
- **Success Criteria**: {what success looks like for this user}
|
||||
|
||||
## Goals & Success Metrics
|
||||
|
||||
| Goal ID | Goal | Success Metric | Target |
|
||||
|---------|------|----------------|--------|
|
||||
| G-001 | {goal description} | {measurable metric} | {specific target} |
|
||||
| G-002 | {goal description} | {measurable metric} | {specific target} |
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
- {feature/capability 1}
|
||||
- {feature/capability 2}
|
||||
- {feature/capability 3}
|
||||
|
||||
### Out of Scope
|
||||
- {explicitly excluded item 1}
|
||||
- {explicitly excluded item 2}
|
||||
|
||||
### Assumptions
|
||||
- {key assumption 1}
|
||||
- {key assumption 2}
|
||||
|
||||
## Competitive Landscape
|
||||
|
||||
| Aspect | Current State | Proposed Solution | Advantage |
|
||||
|--------|--------------|-------------------|-----------|
|
||||
| {aspect} | {how it's done now} | {our approach} | {differentiator} |
|
||||
|
||||
## Constraints & Dependencies
|
||||
|
||||
### Technical Constraints
|
||||
- {constraint 1}
|
||||
- {constraint 2}
|
||||
|
||||
### Business Constraints
|
||||
- {constraint 1}
|
||||
|
||||
### Dependencies
|
||||
- {external dependency 1}
|
||||
- {external dependency 2}
|
||||
|
||||
## Multi-Perspective Synthesis
|
||||
|
||||
### Product Perspective
|
||||
{summary of product/market analysis findings}
|
||||
|
||||
### Technical Perspective
|
||||
{summary of technical feasibility and constraints}
|
||||
|
||||
### User Perspective
|
||||
{summary of user journey and UX considerations}
|
||||
|
||||
### Convergent Themes
|
||||
{themes where all perspectives agree}
|
||||
|
||||
### Conflicting Views
|
||||
{areas where perspectives differ, with notes on resolution approach}
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] {unresolved question 1}
|
||||
- [ ] {unresolved question 2}
|
||||
|
||||
## References
|
||||
|
||||
- Derived from: [spec-config.json](spec-config.json)
|
||||
- Next: [Requirements PRD](requirements.md)
|
||||
```
|
||||
|
||||
## Variable Descriptions
|
||||
|
||||
| Variable | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| `{session_id}` | spec-config.json | Session identifier |
|
||||
| `{timestamp}` | Runtime | ISO8601 generation timestamp |
|
||||
| `{product_name}` | Seed analysis | Product/feature name |
|
||||
| `{executive_summary}` | CLI synthesis | 2-3 sentence summary |
|
||||
| `{vision_statement}` | CLI product perspective | Aspirational vision |
|
||||
| All `{...}` fields | CLI analysis outputs | Filled from multi-perspective analysis |
|
||||
@@ -1,224 +0,0 @@
|
||||
# Requirements PRD Template (Directory Structure)
|
||||
|
||||
Template for generating Product Requirements Document as a directory of individual requirement files in Phase 3.
|
||||
|
||||
## Usage Context
|
||||
|
||||
| Phase | Usage |
|
||||
|-------|-------|
|
||||
| Phase 3 (Requirements) | Generate `requirements/` directory from product brief expansion |
|
||||
| Output Location | `{workDir}/requirements/` |
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
{workDir}/requirements/
|
||||
├── _index.md # Summary + MoSCoW table + traceability matrix + links
|
||||
├── REQ-001-{slug}.md # Individual functional requirement
|
||||
├── REQ-002-{slug}.md
|
||||
├── NFR-P-001-{slug}.md # Non-functional: Performance
|
||||
├── NFR-S-001-{slug}.md # Non-functional: Security
|
||||
├── NFR-SC-001-{slug}.md # Non-functional: Scalability
|
||||
├── NFR-U-001-{slug}.md # Non-functional: Usability
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: _index.md
|
||||
|
||||
```markdown
|
||||
---
|
||||
session_id: {session_id}
|
||||
phase: 3
|
||||
document_type: requirements-index
|
||||
status: draft
|
||||
generated_at: {timestamp}
|
||||
version: 1
|
||||
dependencies:
|
||||
- ../spec-config.json
|
||||
- ../product-brief.md
|
||||
---
|
||||
|
||||
# Requirements: {product_name}
|
||||
|
||||
{executive_summary - brief overview of what this PRD covers and key decisions}
|
||||
|
||||
## Requirement Summary
|
||||
|
||||
| Priority | Count | Coverage |
|
||||
|----------|-------|----------|
|
||||
| Must Have | {n} | {description of must-have scope} |
|
||||
| Should Have | {n} | {description of should-have scope} |
|
||||
| Could Have | {n} | {description of could-have scope} |
|
||||
| Won't Have | {n} | {description of explicitly excluded} |
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
| ID | Title | Priority | Traces To |
|
||||
|----|-------|----------|-----------|
|
||||
| [REQ-001](REQ-001-{slug}.md) | {title} | Must | [G-001](../product-brief.md#goals--success-metrics) |
|
||||
| [REQ-002](REQ-002-{slug}.md) | {title} | Must | [G-001](../product-brief.md#goals--success-metrics) |
|
||||
| [REQ-003](REQ-003-{slug}.md) | {title} | Should | [G-002](../product-brief.md#goals--success-metrics) |
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### Performance
|
||||
|
||||
| ID | Title | Target |
|
||||
|----|-------|--------|
|
||||
| [NFR-P-001](NFR-P-001-{slug}.md) | {title} | {target value} |
|
||||
|
||||
### Security
|
||||
|
||||
| ID | Title | Standard |
|
||||
|----|-------|----------|
|
||||
| [NFR-S-001](NFR-S-001-{slug}.md) | {title} | {standard/framework} |
|
||||
|
||||
### Scalability
|
||||
|
||||
| ID | Title | Target |
|
||||
|----|-------|--------|
|
||||
| [NFR-SC-001](NFR-SC-001-{slug}.md) | {title} | {target value} |
|
||||
|
||||
### Usability
|
||||
|
||||
| ID | Title | Target |
|
||||
|----|-------|--------|
|
||||
| [NFR-U-001](NFR-U-001-{slug}.md) | {title} | {target value} |
|
||||
|
||||
## Data Requirements
|
||||
|
||||
### Data Entities
|
||||
|
||||
| Entity | Description | Key Attributes |
|
||||
|--------|-------------|----------------|
|
||||
| {entity_name} | {description} | {attr1, attr2, attr3} |
|
||||
|
||||
### Data Flows
|
||||
|
||||
{description of key data flows, optionally with Mermaid diagram}
|
||||
|
||||
## Integration Requirements
|
||||
|
||||
| System | Direction | Protocol | Data Format | Notes |
|
||||
|--------|-----------|----------|-------------|-------|
|
||||
| {system_name} | Inbound/Outbound/Both | {REST/gRPC/etc} | {JSON/XML/etc} | {notes} |
|
||||
|
||||
## Constraints & Assumptions
|
||||
|
||||
### Constraints
|
||||
- {technical or business constraint 1}
|
||||
- {technical or business constraint 2}
|
||||
|
||||
### Assumptions
|
||||
- {assumption 1 - must be validated}
|
||||
- {assumption 2 - must be validated}
|
||||
|
||||
## Priority Rationale
|
||||
|
||||
{explanation of MoSCoW prioritization decisions, especially for Should/Could boundaries}
|
||||
|
||||
## Traceability Matrix
|
||||
|
||||
| Goal | Requirements |
|
||||
|------|-------------|
|
||||
| G-001 | [REQ-001](REQ-001-{slug}.md), [REQ-002](REQ-002-{slug}.md), [NFR-P-001](NFR-P-001-{slug}.md) |
|
||||
| G-002 | [REQ-003](REQ-003-{slug}.md), [NFR-S-001](NFR-S-001-{slug}.md) |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] {unresolved question 1}
|
||||
- [ ] {unresolved question 2}
|
||||
|
||||
## References
|
||||
|
||||
- Derived from: [Product Brief](../product-brief.md)
|
||||
- Next: [Architecture](../architecture/_index.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: REQ-NNN-{slug}.md (Individual Functional Requirement)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: REQ-{NNN}
|
||||
type: functional
|
||||
priority: {Must|Should|Could|Won't}
|
||||
traces_to: [G-{NNN}]
|
||||
status: draft
|
||||
---
|
||||
|
||||
# REQ-{NNN}: {requirement_title}
|
||||
|
||||
**Priority**: {Must|Should|Could|Won't}
|
||||
|
||||
## Description
|
||||
|
||||
{detailed requirement description}
|
||||
|
||||
## User Story
|
||||
|
||||
As a {persona}, I want to {action} so that {benefit}.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] {specific, testable criterion 1}
|
||||
- [ ] {specific, testable criterion 2}
|
||||
- [ ] {specific, testable criterion 3}
|
||||
|
||||
## Traces
|
||||
|
||||
- **Goal**: [G-{NNN}](../product-brief.md#goals--success-metrics)
|
||||
- **Architecture**: [ADR-{NNN}](../architecture/ADR-{NNN}-{slug}.md) (if applicable)
|
||||
- **Implemented by**: [EPIC-{NNN}](../epics/EPIC-{NNN}-{slug}.md) (added in Phase 5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template: NFR-{type}-NNN-{slug}.md (Individual Non-Functional Requirement)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: NFR-{type}-{NNN}
|
||||
type: non-functional
|
||||
category: {Performance|Security|Scalability|Usability}
|
||||
priority: {Must|Should|Could}
|
||||
status: draft
|
||||
---
|
||||
|
||||
# NFR-{type}-{NNN}: {requirement_title}
|
||||
|
||||
**Category**: {Performance|Security|Scalability|Usability}
|
||||
**Priority**: {Must|Should|Could}
|
||||
|
||||
## Requirement
|
||||
|
||||
{detailed requirement description}
|
||||
|
||||
## Metric & Target
|
||||
|
||||
| Metric | Target | Measurement Method |
|
||||
|--------|--------|--------------------|
|
||||
| {metric} | {target value} | {how measured} |
|
||||
|
||||
## Traces
|
||||
|
||||
- **Goal**: [G-{NNN}](../product-brief.md#goals--success-metrics)
|
||||
- **Architecture**: [ADR-{NNN}](../architecture/ADR-{NNN}-{slug}.md) (if applicable)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variable Descriptions
|
||||
|
||||
| Variable | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| `{session_id}` | spec-config.json | Session identifier |
|
||||
| `{timestamp}` | Runtime | ISO8601 generation timestamp |
|
||||
| `{product_name}` | product-brief.md | Product/feature name |
|
||||
| `{NNN}` | Auto-increment | Requirement number (zero-padded 3 digits) |
|
||||
| `{slug}` | Auto-generated | Kebab-case from requirement title |
|
||||
| `{type}` | Category | P (Performance), S (Security), SC (Scalability), U (Usability) |
|
||||
| `{Must\|Should\|Could\|Won't}` | User input / auto | MoSCoW priority tag |
|
||||
Reference in New Issue
Block a user