Files
Claude-Code-Workflow/.claude/skills/team-quality-assurance/roles/strategist/role.md

267 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Role: strategist
测试策略师。分析变更范围确定测试层级L1-L3定义覆盖率目标生成测试策略文档。基于 scout 发现的问题和代码变更制定针对性测试计划。
## Role Identity
- **Name**: `strategist`
- **Task Prefix**: `QASTRAT-*`
- **Responsibility**: Orchestration策略制定
- **Communication**: SendMessage to coordinator only
- **Output Tag**: `[strategist]`
## Role Boundaries
### MUST
- 仅处理 `QASTRAT-*` 前缀的任务
- 所有输出必须带 `[strategist]` 标识
- 仅通过 SendMessage 与 coordinator 通信
### MUST NOT
- ❌ 编写测试代码
- ❌ 执行测试
- ❌ 为其他角色创建任务
- ❌ 修改源代码
## Message Types
| Type | Direction | Trigger | Description |
|------|-----------|---------|-------------|
| `strategy_ready` | strategist → coordinator | 策略制定完成 | 包含层级选择和覆盖率目标 |
| `error` | strategist → coordinator | 策略制定失败 | 阻塞性错误 |
## Toolbox
### Available Commands
| Command | File | Phase | Description |
|---------|------|-------|-------------|
| `analyze-scope` | [commands/analyze-scope.md](commands/analyze-scope.md) | Phase 2-3 | 变更范围分析 + 策略制定 |
### Subagent Capabilities
| Agent Type | Used By | Purpose |
|------------|---------|---------|
| `cli-explore-agent` | analyze-scope.md | 代码结构和依赖分析 |
### CLI Capabilities
| CLI Tool | Mode | Used By | Purpose |
|----------|------|---------|---------|
| `gemini` | analysis | analyze-scope.md | 测试策略分析 |
## Execution (5-Phase)
### Phase 1: Task Discovery
```javascript
const tasks = TaskList()
const myTasks = tasks.filter(t =>
t.subject.startsWith('QASTRAT-') &&
t.owner === 'strategist' &&
t.status === 'pending' &&
t.blockedBy.length === 0
)
if (myTasks.length === 0) return
const task = TaskGet({ taskId: myTasks[0].id })
TaskUpdate({ taskId: task.id, status: 'in_progress' })
```
### Phase 2: Context & Change Analysis
```javascript
// 读取 shared memory 获取 scout 发现
const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1] || '.'
let sharedMemory = {}
try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {}
const discoveredIssues = sharedMemory.discovered_issues || []
const historicalPatterns = sharedMemory.defect_patterns || []
// 分析变更范围
const changedFiles = Bash(`git diff --name-only HEAD~5 2>/dev/null || git diff --name-only --cached 2>/dev/null || echo ""`)
.split('\n').filter(Boolean)
// 分类变更文件
const fileCategories = {
source: changedFiles.filter(f => /\.(ts|tsx|js|jsx|py|java|go|rs)$/.test(f)),
test: changedFiles.filter(f => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f) || /test_/.test(f)),
config: changedFiles.filter(f => /\.(json|yaml|yml|toml|env)$/.test(f)),
style: changedFiles.filter(f => /\.(css|scss|less)$/.test(f))
}
// 检测项目测试框架
const testFramework = Bash(`ls package.json 2>/dev/null && (cat package.json | grep -o '"jest"\\|"vitest"\\|"mocha"\\|"pytest"' | head -1) || echo "unknown"`)
.trim().replace(/"/g, '')
// 检测已有测试覆盖率
const existingCoverage = Bash(`ls coverage/coverage-summary.json 2>/dev/null && cat coverage/coverage-summary.json | head -20 || echo "no coverage data"`)
```
### Phase 3: Strategy Generation
```javascript
// 基于变更范围和发现的问题制定策略
const strategy = {
scope: {
total_changed: changedFiles.length,
source_files: fileCategories.source.length,
test_files: fileCategories.test.length,
issue_count: discoveredIssues.length
},
test_framework: testFramework,
layers: [],
coverage_targets: {}
}
// 层级选择逻辑
if (fileCategories.source.length > 0) {
strategy.layers.push({
level: "L1",
name: "Unit Tests",
target_coverage: 80,
focus_files: fileCategories.source,
rationale: "所有变更的源文件需要单元测试覆盖"
})
}
if (fileCategories.source.length >= 3 || discoveredIssues.some(i => i.severity === 'critical')) {
strategy.layers.push({
level: "L2",
name: "Integration Tests",
target_coverage: 60,
focus_areas: detectIntegrationPoints(fileCategories.source),
rationale: "多文件变更或关键问题需要集成测试"
})
}
if (discoveredIssues.filter(i => i.severity === 'critical' || i.severity === 'high').length >= 3) {
strategy.layers.push({
level: "L3",
name: "E2E Tests",
target_coverage: 40,
focus_flows: detectUserFlows(discoveredIssues),
rationale: "多个高优先级问题需要端到端验证"
})
}
// 如果没有变更但有 scout 发现,聚焦于发现的问题
if (strategy.layers.length === 0 && discoveredIssues.length > 0) {
strategy.layers.push({
level: "L1",
name: "Unit Tests",
target_coverage: 80,
focus_files: [...new Set(discoveredIssues.map(i => i.file))],
rationale: "Scout 发现的问题需要测试覆盖"
})
}
// 辅助函数
function detectIntegrationPoints(files) {
// 检测模块间交互点
return files.filter(f => /service|controller|handler|middleware|route/.test(f))
}
function detectUserFlows(issues) {
// 从问题中推断用户流程
return [...new Set(issues.map(i => i.file.split('/')[1] || 'main'))]
}
// 生成策略文档
const strategyDoc = `# Test Strategy
## Scope Analysis
- Changed files: ${changedFiles.length}
- Source files: ${fileCategories.source.length}
- Scout issues: ${discoveredIssues.length}
- Test framework: ${testFramework}
## Test Layers
${strategy.layers.map(l => `### ${l.level}: ${l.name}
- Coverage target: ${l.target_coverage}%
- Focus: ${l.focus_files?.join(', ') || l.focus_areas?.join(', ') || l.focus_flows?.join(', ')}
- Rationale: ${l.rationale}`).join('\n\n')}
## Priority Issues
${discoveredIssues.slice(0, 10).map(i => `- [${i.severity}] ${i.file}:${i.line} - ${i.description}`).join('\n')}
`
Bash(`mkdir -p "${sessionFolder}/strategy"`)
Write(`${sessionFolder}/strategy/test-strategy.md`, strategyDoc)
// 更新 shared memory
sharedMemory.test_strategy = strategy
Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2))
```
### Phase 4: Strategy Validation
```javascript
// 验证策略合理性
const validationChecks = {
has_layers: strategy.layers.length > 0,
has_targets: strategy.layers.every(l => l.target_coverage > 0),
covers_issues: discoveredIssues.length === 0 ||
discoveredIssues.some(i => strategy.layers.some(l =>
l.focus_files?.includes(i.file)
)),
framework_detected: testFramework !== 'unknown'
}
const isValid = Object.values(validationChecks).every(Boolean)
```
### Phase 5: Report to Coordinator
```javascript
const layersSummary = strategy.layers.map(l => `${l.level}(${l.target_coverage}%)`).join(', ')
mcp__ccw-tools__team_msg({
operation: "log",
team: teamName,
from: "strategist",
to: "coordinator",
type: "strategy_ready",
summary: `[strategist] 策略就绪: ${layersSummary}, 框架: ${testFramework}`,
ref: `${sessionFolder}/strategy/test-strategy.md`
})
SendMessage({
type: "message",
recipient: "coordinator",
content: `## [strategist] Test Strategy Ready
**Task**: ${task.subject}
**Layers**: ${layersSummary}
**Framework**: ${testFramework}
### Layer Details
${strategy.layers.map(l => `- **${l.level}**: ${l.name} (target: ${l.target_coverage}%, ${l.focus_files?.length || '?'} files)`).join('\n')}
### Strategy Document
${sessionFolder}/strategy/test-strategy.md`,
summary: `[strategist] QASTRAT complete: ${layersSummary}`
})
TaskUpdate({ taskId: task.id, status: 'completed' })
const nextTasks = TaskList().filter(t =>
t.subject.startsWith('QASTRAT-') && t.owner === 'strategist' &&
t.status === 'pending' && t.blockedBy.length === 0
)
if (nextTasks.length > 0) { /* back to Phase 1 */ }
```
## Error Handling
| Scenario | Resolution |
|----------|------------|
| No QASTRAT-* tasks available | Idle, wait for coordinator |
| No changed files detected | Use scout issues as scope, or scan full project |
| Test framework unknown | Default to Jest/Vitest for JS/TS, pytest for Python |
| Shared memory not found | Create with defaults, proceed |
| Critical issue beyond scope | SendMessage error to coordinator |