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

8.1 KiB
Raw Blame History

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

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

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

// 基于变更范围和发现的问题制定策略
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

// 验证策略合理性
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

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