mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
# Command: generate-tests
|
||||
|
||||
> 按层级生成测试代码。根据 strategist 策略和项目现有测试模式,生成 L1/L2/L3 测试用例。
|
||||
|
||||
## When to Use
|
||||
|
||||
- Phase 3 of Generator
|
||||
- 策略已制定,需要生成对应层级的测试代码
|
||||
- GC 循环中修订失败的测试
|
||||
|
||||
**Trigger conditions**:
|
||||
- QAGEN-* 任务进入执行阶段
|
||||
- 测试策略中包含当前层级
|
||||
- GC 循环触发修复任务(QAGEN-fix-*)
|
||||
|
||||
## Strategy
|
||||
|
||||
### Delegation Mode
|
||||
|
||||
**Mode**: Sequential Delegation(复杂时)/ Direct(简单时)
|
||||
**Agent Type**: `code-developer`
|
||||
**Delegation Scope**: Per-layer
|
||||
|
||||
### Decision Logic
|
||||
|
||||
```javascript
|
||||
const focusFiles = layerConfig.focus_files || []
|
||||
const isGCFix = task.subject.includes('fix')
|
||||
|
||||
if (isGCFix) {
|
||||
// GC 修复模式:读取失败信息,针对性修复
|
||||
mode = 'gc-fix'
|
||||
} else if (focusFiles.length <= 3) {
|
||||
// 直接生成:内联 Read → 分析 → Write
|
||||
mode = 'direct'
|
||||
} else {
|
||||
// 委派给 code-developer
|
||||
mode = 'delegate'
|
||||
}
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Context Preparation
|
||||
|
||||
```javascript
|
||||
// 从 shared memory 获取策略
|
||||
const strategy = sharedMemory.test_strategy || {}
|
||||
const targetLayer = task.description.match(/layer:\s*(L[123])/)?.[1] || 'L1'
|
||||
|
||||
// 确定层级配置
|
||||
const layerConfig = strategy.layers?.find(l => l.level === targetLayer) || {
|
||||
level: targetLayer,
|
||||
name: targetLayer === 'L1' ? 'Unit Tests' : targetLayer === 'L2' ? 'Integration Tests' : 'E2E Tests',
|
||||
target_coverage: targetLayer === 'L1' ? 80 : targetLayer === 'L2' ? 60 : 40,
|
||||
focus_files: []
|
||||
}
|
||||
|
||||
// 学习现有测试模式(必须找 3 个相似测试文件)
|
||||
const existingTests = Glob(`**/*.{test,spec}.{ts,tsx,js,jsx}`)
|
||||
const testPatterns = existingTests.slice(0, 3).map(f => ({
|
||||
path: f,
|
||||
content: Read(f)
|
||||
}))
|
||||
|
||||
// 检测测试约定
|
||||
const testConventions = detectTestConventions(testPatterns)
|
||||
```
|
||||
|
||||
### Step 2: Execute Strategy
|
||||
|
||||
```javascript
|
||||
if (mode === 'gc-fix') {
|
||||
// GC 修复模式
|
||||
// 读取失败信息
|
||||
const failedTests = sharedMemory.execution_results?.[targetLayer]
|
||||
const failureOutput = Read(`${sessionFolder}/results/run-${targetLayer}.json`)
|
||||
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: `Fix failing ${targetLayer} tests (GC iteration)`,
|
||||
prompt: `## Goal
|
||||
Fix the failing tests based on execution results. Do NOT modify source code.
|
||||
|
||||
## Test Execution Results
|
||||
${JSON.stringify(failedTests, null, 2)}
|
||||
|
||||
## Test Conventions
|
||||
${JSON.stringify(testConventions, null, 2)}
|
||||
|
||||
## Instructions
|
||||
- Read each failing test file
|
||||
- Fix assertions, imports, mocks, or test setup
|
||||
- Ensure tests match actual source behavior
|
||||
- Do NOT skip or ignore tests
|
||||
- Do NOT modify source files`
|
||||
})
|
||||
|
||||
} else if (mode === 'direct') {
|
||||
// 直接生成模式
|
||||
const focusFiles = layerConfig.focus_files || []
|
||||
|
||||
for (const sourceFile of focusFiles) {
|
||||
const sourceContent = Read(sourceFile)
|
||||
|
||||
// 确定测试文件路径(遵循项目约定)
|
||||
const testPath = determineTestPath(sourceFile, testConventions)
|
||||
|
||||
// 检查是否已有测试
|
||||
let existingTest = null
|
||||
try { existingTest = Read(testPath) } catch {}
|
||||
|
||||
if (existingTest) {
|
||||
// 补充现有测试:分析缺失的测试用例
|
||||
const missingCases = analyzeMissingCases(sourceContent, existingTest)
|
||||
if (missingCases.length > 0) {
|
||||
// 追加测试用例
|
||||
Edit({
|
||||
file_path: testPath,
|
||||
old_string: findLastTestBlock(existingTest),
|
||||
new_string: `${findLastTestBlock(existingTest)}\n\n${generateCases(missingCases, testConventions)}`
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 创建新测试文件
|
||||
const testContent = generateFullTestFile(sourceFile, sourceContent, testConventions, targetLayer)
|
||||
Write(testPath, testContent)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 委派模式
|
||||
const focusFiles = layerConfig.focus_files || []
|
||||
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: `Generate ${targetLayer} tests for ${focusFiles.length} files`,
|
||||
prompt: `## Goal
|
||||
Generate ${layerConfig.name} for the following source files.
|
||||
|
||||
## Test Framework
|
||||
${strategy.test_framework || 'vitest'}
|
||||
|
||||
## Existing Test Patterns (MUST follow these exactly)
|
||||
${testPatterns.map(t => `### ${t.path}\n\`\`\`\n${t.content.substring(0, 800)}\n\`\`\``).join('\n\n')}
|
||||
|
||||
## Test Conventions
|
||||
- Test file location: ${testConventions.location}
|
||||
- Import style: ${testConventions.importStyle}
|
||||
- Describe/it nesting: ${testConventions.nesting}
|
||||
|
||||
## Source Files to Test
|
||||
${focusFiles.map(f => `- ${f}`).join('\n')}
|
||||
|
||||
## Requirements
|
||||
- Follow existing test patterns exactly (import style, naming, structure)
|
||||
- Cover: happy path + edge cases + error cases
|
||||
- Target coverage: ${layerConfig.target_coverage}%
|
||||
- Do NOT modify source files, only create/modify test files
|
||||
- Do NOT use \`any\` type assertions
|
||||
- Do NOT skip or mark tests as TODO without implementation`
|
||||
})
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
function determineTestPath(sourceFile, conventions) {
|
||||
if (conventions.location === 'colocated') {
|
||||
return sourceFile.replace(/\.(ts|tsx|js|jsx)$/, `.test.$1`)
|
||||
} else if (conventions.location === '__tests__') {
|
||||
const dir = sourceFile.substring(0, sourceFile.lastIndexOf('/'))
|
||||
const name = sourceFile.substring(sourceFile.lastIndexOf('/') + 1)
|
||||
return `${dir}/__tests__/${name.replace(/\.(ts|tsx|js|jsx)$/, `.test.$1`)}`
|
||||
}
|
||||
return sourceFile.replace(/\.(ts|tsx|js|jsx)$/, `.test.$1`)
|
||||
}
|
||||
|
||||
function detectTestConventions(patterns) {
|
||||
const conventions = {
|
||||
location: 'colocated', // or '__tests__'
|
||||
importStyle: 'named', // or 'default'
|
||||
nesting: 'describe-it', // or 'test-only'
|
||||
framework: 'vitest'
|
||||
}
|
||||
|
||||
for (const p of patterns) {
|
||||
if (p.path.includes('__tests__')) conventions.location = '__tests__'
|
||||
if (p.content.includes("import { describe")) conventions.nesting = 'describe-it'
|
||||
if (p.content.includes("from 'vitest'")) conventions.framework = 'vitest'
|
||||
if (p.content.includes("from '@jest'") || p.content.includes("from 'jest'")) conventions.framework = 'jest'
|
||||
}
|
||||
|
||||
return conventions
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Result Processing
|
||||
|
||||
```javascript
|
||||
// 收集生成/修改的测试文件
|
||||
const generatedTests = Bash(`git diff --name-only`).split('\n')
|
||||
.filter(f => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f))
|
||||
|
||||
// TypeScript 语法检查
|
||||
const syntaxResult = Bash(`npx tsc --noEmit ${generatedTests.join(' ')} 2>&1 || true`)
|
||||
const hasSyntaxErrors = syntaxResult.includes('error TS')
|
||||
|
||||
// 自动修复语法错误(最多 3 次)
|
||||
if (hasSyntaxErrors) {
|
||||
let fixAttempt = 0
|
||||
while (fixAttempt < 3 && syntaxResult.includes('error TS')) {
|
||||
const errors = syntaxResult.split('\n').filter(l => l.includes('error TS')).slice(0, 5)
|
||||
// 尝试修复每个错误...
|
||||
fixAttempt++
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 shared memory
|
||||
const testInfo = {
|
||||
layer: targetLayer,
|
||||
files: generatedTests,
|
||||
count: generatedTests.length,
|
||||
syntax_clean: !hasSyntaxErrors,
|
||||
mode: mode,
|
||||
gc_fix: mode === 'gc-fix'
|
||||
}
|
||||
|
||||
sharedMemory.generated_tests = sharedMemory.generated_tests || {}
|
||||
sharedMemory.generated_tests[targetLayer] = testInfo
|
||||
Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
## Test Generation Results
|
||||
|
||||
### Layer: [L1|L2|L3]
|
||||
### Mode: [direct|delegate|gc-fix]
|
||||
### Files Generated: [count]
|
||||
- [test file path]
|
||||
|
||||
### Syntax Check: PASS/FAIL
|
||||
### Conventions Applied: [framework], [location], [nesting]
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No focus files in strategy | Generate L1 tests for all source files in scope |
|
||||
| No existing test patterns | Use framework defaults (vitest/jest/pytest) |
|
||||
| Sub-agent failure | Retry once, fallback to direct generation |
|
||||
| Syntax errors persist after 3 fixes | Report errors, proceed with available tests |
|
||||
| Source file not found | Skip file, log warning |
|
||||
| Agent/CLI failure | Retry once, then fallback to inline execution |
|
||||
| Timeout (>5 min) | Report partial results, notify coordinator |
|
||||
282
.claude/skills/team-quality-assurance/roles/generator/role.md
Normal file
282
.claude/skills/team-quality-assurance/roles/generator/role.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Role: generator
|
||||
|
||||
测试用例生成器。按 strategist 制定的策略和层级,生成对应的测试代码。支持 L1 单元测试、L2 集成测试、L3 E2E 测试。遵循项目现有测试模式和框架约定。
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `generator`
|
||||
- **Task Prefix**: `QAGEN-*`
|
||||
- **Responsibility**: Code generation(测试代码生成)
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
- **Output Tag**: `[generator]`
|
||||
|
||||
## Role Boundaries
|
||||
|
||||
### MUST
|
||||
|
||||
- 仅处理 `QAGEN-*` 前缀的任务
|
||||
- 所有输出必须带 `[generator]` 标识
|
||||
- 遵循项目现有测试框架和模式
|
||||
- 生成的测试必须可运行
|
||||
|
||||
### MUST NOT
|
||||
|
||||
- ❌ 修改源代码(仅生成测试代码)
|
||||
- ❌ 执行测试
|
||||
- ❌ 为其他角色创建任务
|
||||
- ❌ 直接与其他 worker 通信
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `tests_generated` | generator → coordinator | 测试生成完成 | 包含生成的测试文件列表 |
|
||||
| `tests_revised` | generator → coordinator | 测试修订完成 | GC 循环中修订后 |
|
||||
| `error` | generator → coordinator | 生成失败 | 阻塞性错误 |
|
||||
|
||||
## Toolbox
|
||||
|
||||
### Available Commands
|
||||
|
||||
| Command | File | Phase | Description |
|
||||
|---------|------|-------|-------------|
|
||||
| `generate-tests` | [commands/generate-tests.md](commands/generate-tests.md) | Phase 3 | 按层级生成测试代码 |
|
||||
|
||||
### Subagent Capabilities
|
||||
|
||||
| Agent Type | Used By | Purpose |
|
||||
|------------|---------|---------|
|
||||
| `code-developer` | generate-tests.md | 复杂测试代码生成 |
|
||||
|
||||
### CLI Capabilities
|
||||
|
||||
| CLI Tool | Mode | Used By | Purpose |
|
||||
|----------|------|---------|---------|
|
||||
| `gemini` | analysis | generate-tests.md | 分析现有测试模式 |
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
t.subject.startsWith('QAGEN-') &&
|
||||
t.owner === 'generator' &&
|
||||
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: Strategy & Pattern Loading
|
||||
|
||||
```javascript
|
||||
// 读取 shared memory 获取策略
|
||||
const sessionFolder = task.description.match(/session:\s*(.+)/)?.[1] || '.'
|
||||
let sharedMemory = {}
|
||||
try { sharedMemory = JSON.parse(Read(`${sessionFolder}/shared-memory.json`)) } catch {}
|
||||
|
||||
const strategy = sharedMemory.test_strategy || {}
|
||||
const targetLayer = task.description.match(/layer:\s*(L[123])/)?.[1] || strategy.layers?.[0]?.level || 'L1'
|
||||
|
||||
// 确定目标层级的详情
|
||||
const layerConfig = strategy.layers?.find(l => l.level === targetLayer) || {
|
||||
level: targetLayer,
|
||||
name: targetLayer === 'L1' ? 'Unit Tests' : targetLayer === 'L2' ? 'Integration Tests' : 'E2E Tests',
|
||||
target_coverage: targetLayer === 'L1' ? 80 : targetLayer === 'L2' ? 60 : 40,
|
||||
focus_files: []
|
||||
}
|
||||
|
||||
// 学习现有测试模式(找 3 个相似测试文件)
|
||||
const existingTests = Glob(`**/*.{test,spec}.{ts,tsx,js,jsx}`)
|
||||
const testPatterns = existingTests.slice(0, 3).map(f => ({
|
||||
path: f,
|
||||
content: Read(f)
|
||||
}))
|
||||
|
||||
// 检测测试框架和配置
|
||||
const testFramework = strategy.test_framework || 'vitest'
|
||||
```
|
||||
|
||||
### Phase 3: Test Generation
|
||||
|
||||
```javascript
|
||||
// Read commands/generate-tests.md for full implementation
|
||||
Read("commands/generate-tests.md")
|
||||
```
|
||||
|
||||
**核心策略**: 基于复杂度选择生成方式
|
||||
|
||||
```javascript
|
||||
const focusFiles = layerConfig.focus_files || []
|
||||
|
||||
if (focusFiles.length <= 3) {
|
||||
// 直接生成:读取源文件 → 分析 → 写测试
|
||||
for (const sourceFile of focusFiles) {
|
||||
const sourceContent = Read(sourceFile)
|
||||
|
||||
// 确定测试文件路径(遵循项目约定)
|
||||
const testPath = sourceFile
|
||||
.replace(/\.(ts|tsx|js|jsx)$/, `.test.$1`)
|
||||
.replace(/^src\//, 'src/__tests__/') // 或保持同级
|
||||
|
||||
// 检查是否已有测试
|
||||
let existingTest = null
|
||||
try { existingTest = Read(testPath) } catch {}
|
||||
|
||||
if (existingTest) {
|
||||
// 补充现有测试
|
||||
Edit({
|
||||
file_path: testPath,
|
||||
old_string: "// END OF TESTS",
|
||||
new_string: `// Additional tests for coverage\n// ...new test cases...\n// END OF TESTS`
|
||||
})
|
||||
} else {
|
||||
// 创建新测试文件
|
||||
Write(testPath, generateTestContent(sourceFile, sourceContent, testPatterns, testFramework))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 委派给 code-developer
|
||||
Task({
|
||||
subagent_type: "code-developer",
|
||||
run_in_background: false,
|
||||
description: `Generate ${targetLayer} tests for ${focusFiles.length} files`,
|
||||
prompt: `## Goal
|
||||
Generate ${layerConfig.name} for the following source files.
|
||||
|
||||
## Test Framework
|
||||
${testFramework}
|
||||
|
||||
## Existing Test Patterns
|
||||
${testPatterns.map(t => `### ${t.path}\n\`\`\`\n${t.content.substring(0, 500)}\n\`\`\``).join('\n\n')}
|
||||
|
||||
## Source Files to Test
|
||||
${focusFiles.map(f => `- ${f}`).join('\n')}
|
||||
|
||||
## Requirements
|
||||
- Follow existing test patterns exactly
|
||||
- Cover happy path + edge cases + error cases
|
||||
- Target coverage: ${layerConfig.target_coverage}%
|
||||
- Do NOT modify source files, only create/modify test files`
|
||||
})
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
function generateTestContent(sourceFile, sourceContent, patterns, framework) {
|
||||
// 基于模式生成测试代码骨架
|
||||
const imports = extractExports(sourceContent)
|
||||
const pattern = patterns[0]?.content || ''
|
||||
|
||||
return `import { ${imports.join(', ')} } from '${sourceFile.replace(/\.(ts|tsx|js|jsx)$/, '')}'
|
||||
|
||||
describe('${sourceFile}', () => {
|
||||
${imports.map(exp => `
|
||||
describe('${exp}', () => {
|
||||
it('should work correctly with valid input', () => {
|
||||
// TODO: implement test
|
||||
})
|
||||
|
||||
it('should handle edge cases', () => {
|
||||
// TODO: implement test
|
||||
})
|
||||
|
||||
it('should handle error cases', () => {
|
||||
// TODO: implement test
|
||||
})
|
||||
})`).join('\n')}
|
||||
})`
|
||||
}
|
||||
|
||||
function extractExports(content) {
|
||||
const matches = content.match(/export\s+(function|const|class|interface|type)\s+(\w+)/g) || []
|
||||
return matches.map(m => m.split(/\s+/).pop())
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Self-Validation
|
||||
|
||||
```javascript
|
||||
// 验证生成的测试文件语法正确
|
||||
const generatedTests = Bash(`git diff --name-only`).split('\n')
|
||||
.filter(f => /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(f))
|
||||
|
||||
// TypeScript 语法检查
|
||||
const syntaxResult = Bash(`npx tsc --noEmit ${generatedTests.join(' ')} 2>&1 || true`)
|
||||
const hasSyntaxErrors = syntaxResult.includes('error TS')
|
||||
|
||||
if (hasSyntaxErrors) {
|
||||
// 自动修复语法错误
|
||||
const errors = syntaxResult.split('\n').filter(l => l.includes('error TS'))
|
||||
for (const error of errors.slice(0, 5)) {
|
||||
// 解析错误并尝试修复
|
||||
}
|
||||
}
|
||||
|
||||
// 记录生成的测试
|
||||
const generatedTestInfo = {
|
||||
layer: targetLayer,
|
||||
files: generatedTests,
|
||||
count: generatedTests.length,
|
||||
syntax_clean: !hasSyntaxErrors
|
||||
}
|
||||
|
||||
// 更新 shared memory
|
||||
sharedMemory.generated_tests = sharedMemory.generated_tests || {}
|
||||
sharedMemory.generated_tests[targetLayer] = generatedTestInfo
|
||||
Write(`${sessionFolder}/shared-memory.json`, JSON.stringify(sharedMemory, null, 2))
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator
|
||||
|
||||
```javascript
|
||||
const msgType = task.subject.includes('fix') ? 'tests_revised' : 'tests_generated'
|
||||
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log",
|
||||
team: teamName,
|
||||
from: "generator",
|
||||
to: "coordinator",
|
||||
type: msgType,
|
||||
summary: `[generator] ${targetLayer} 测试生成完成: ${generatedTests.length} 文件, 语法${hasSyntaxErrors ? '有错误' : '正常'}`,
|
||||
ref: generatedTests[0]
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## [generator] Test Generation Results
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Layer**: ${targetLayer} - ${layerConfig.name}
|
||||
**Generated**: ${generatedTests.length} test files
|
||||
**Syntax**: ${hasSyntaxErrors ? 'ERRORS' : 'CLEAN'}
|
||||
|
||||
### Generated Files
|
||||
${generatedTests.map(f => `- ${f}`).join('\n')}`,
|
||||
summary: `[generator] QAGEN complete: ${targetLayer} ${generatedTests.length} files`
|
||||
})
|
||||
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
|
||||
const nextTasks = TaskList().filter(t =>
|
||||
t.subject.startsWith('QAGEN-') && t.owner === 'generator' &&
|
||||
t.status === 'pending' && t.blockedBy.length === 0
|
||||
)
|
||||
if (nextTasks.length > 0) { /* back to Phase 1 */ }
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No QAGEN-* tasks available | Idle, wait for coordinator |
|
||||
| Strategy not found in shared memory | Generate L1 unit tests for changed files |
|
||||
| No existing test patterns found | Use framework defaults |
|
||||
| Sub-agent failure | Retry once, fallback to direct generation |
|
||||
| Syntax errors in generated tests | Auto-fix up to 3 attempts, report remaining |
|
||||
| Source file not found | Skip file, report to coordinator |
|
||||
Reference in New Issue
Block a user