mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat: merge 10 team commands into unified team-lifecycle skill
Consolidate coordinate, plan, execute, test, review, spec-coordinate, spec-analyst, spec-writer, spec-discuss, spec-reviewer into a single team-lifecycle skill with role-based routing (Pattern B architecture). - SKILL.md: role router with 8 roles, shared message bus, 3-mode pipeline - coordinator: unified orchestrator for spec-only/impl-only/full-lifecycle - 7 worker roles: analyst, writer, discussant, planner, executor, tester, reviewer - reviewer: dual-prefix (REVIEW-*/QUALITY-*) auto-switching code/spec review - Each role: 5-phase execution, message bus with CLI fallback, error handling
This commit is contained in:
508
.claude/skills/team-lifecycle/roles/reviewer.md
Normal file
508
.claude/skills/team-lifecycle/roles/reviewer.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Role: reviewer
|
||||
|
||||
Unified review role handling both code review (REVIEW-*) and specification quality checks (QUALITY-*). Auto-switches behavior based on task prefix.
|
||||
|
||||
## Role Identity
|
||||
|
||||
- **Name**: `reviewer`
|
||||
- **Task Prefix**: `REVIEW-*` + `QUALITY-*`
|
||||
- **Responsibility**: Discover Task → Branch by Prefix → Review/Score → Report
|
||||
- **Communication**: SendMessage to coordinator only
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Trigger | Description |
|
||||
|------|-----------|---------|-------------|
|
||||
| `review_result` | reviewer → coordinator | Code review complete | With verdict (APPROVE/CONDITIONAL/BLOCK) and findings |
|
||||
| `quality_result` | reviewer → coordinator | Spec quality check complete | With score and gate decision (PASS/REVIEW/FAIL) |
|
||||
| `fix_required` | reviewer → coordinator | Critical issues found | Needs IMPL-fix or DRAFT-fix tasks |
|
||||
| `error` | reviewer → coordinator | Review cannot proceed | Plan missing, documents missing, etc. |
|
||||
|
||||
## Message Bus
|
||||
|
||||
Before every `SendMessage`, MUST call `mcp__ccw-tools__team_msg` to log:
|
||||
|
||||
```javascript
|
||||
// Code review result
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "review_result", summary: "REVIEW APPROVE: 8 findings (critical=0, high=2)", data: { verdict: "APPROVE", critical: 0, high: 2 } })
|
||||
|
||||
// Spec quality result
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "quality_result", summary: "Quality check PASS: 85.0 score", data: { gate: "PASS", score: 85.0 } })
|
||||
|
||||
// Fix required
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "fix_required", summary: "Critical security issues found, IMPL-fix needed", data: { critical: 2 } })
|
||||
|
||||
// Error report
|
||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "reviewer", to: "coordinator", type: "error", summary: "plan.json not found, cannot verify requirements" })
|
||||
```
|
||||
|
||||
### CLI Fallback
|
||||
|
||||
When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
Bash(`ccw team log --team "${teamName}" --from "reviewer" --to "coordinator" --type "review_result" --summary "REVIEW APPROVE: 8 findings" --data '{"verdict":"APPROVE","critical":0}' --json`)
|
||||
```
|
||||
|
||||
## Execution (5-Phase)
|
||||
|
||||
### Phase 1: Task Discovery (Dual-Prefix)
|
||||
|
||||
```javascript
|
||||
const tasks = TaskList()
|
||||
const myTasks = tasks.filter(t =>
|
||||
(t.subject.startsWith('REVIEW-') || t.subject.startsWith('QUALITY-')) &&
|
||||
t.owner === 'reviewer' &&
|
||||
t.status === 'pending' &&
|
||||
t.blockedBy.length === 0
|
||||
)
|
||||
|
||||
if (myTasks.length === 0) return // idle
|
||||
|
||||
const task = TaskGet({ taskId: myTasks[0].id })
|
||||
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||
|
||||
// Determine review mode
|
||||
const reviewMode = task.subject.startsWith('REVIEW-') ? 'code' : 'spec'
|
||||
```
|
||||
|
||||
### Phase 2: Context Loading (Branch by Mode)
|
||||
|
||||
**Code Review Mode (REVIEW-*):**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
// Load plan for acceptance criteria
|
||||
const planPathMatch = task.description.match(/\.workflow\/\.team-plan\/[^\s]+\/plan\.json/)
|
||||
let plan = null
|
||||
if (planPathMatch) {
|
||||
try { plan = JSON.parse(Read(planPathMatch[0])) } catch {}
|
||||
}
|
||||
|
||||
// Get changed files via git
|
||||
const changedFiles = Bash(`git diff --name-only HEAD~1 2>/dev/null || git diff --name-only --cached`)
|
||||
.split('\n').filter(f => f.trim() && !f.startsWith('.'))
|
||||
|
||||
// Read changed file contents (limit to 20 files)
|
||||
const fileContents = {}
|
||||
for (const file of changedFiles.slice(0, 20)) {
|
||||
try { fileContents[file] = Read(file) } catch {}
|
||||
}
|
||||
|
||||
// Load test results if available
|
||||
const testSummary = tasks.find(t => t.subject.startsWith('TEST-') && t.status === 'completed')
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality Mode (QUALITY-*):**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
const sessionMatch = task.description.match(/Session:\s*(.+)/)
|
||||
const sessionFolder = sessionMatch ? sessionMatch[1].trim() : ''
|
||||
|
||||
// Load all spec documents
|
||||
const documents = {
|
||||
config: null, discoveryContext: null, productBrief: null,
|
||||
requirementsIndex: null, requirements: [], architectureIndex: null,
|
||||
adrs: [], epicsIndex: null, epics: [], discussions: []
|
||||
}
|
||||
|
||||
try { documents.config = JSON.parse(Read(`${sessionFolder}/spec-config.json`)) } catch {}
|
||||
try { documents.discoveryContext = JSON.parse(Read(`${sessionFolder}/discovery-context.json`)) } catch {}
|
||||
try { documents.productBrief = Read(`${sessionFolder}/product-brief.md`) } catch {}
|
||||
try { documents.requirementsIndex = Read(`${sessionFolder}/requirements/_index.md`) } catch {}
|
||||
try { documents.architectureIndex = Read(`${sessionFolder}/architecture/_index.md`) } catch {}
|
||||
try { documents.epicsIndex = Read(`${sessionFolder}/epics/_index.md`) } catch {}
|
||||
|
||||
// Load individual documents
|
||||
Glob({ pattern: `${sessionFolder}/requirements/REQ-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/requirements/NFR-*.md` }).forEach(f => { try { documents.requirements.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/architecture/ADR-*.md` }).forEach(f => { try { documents.adrs.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/epics/EPIC-*.md` }).forEach(f => { try { documents.epics.push(Read(f)) } catch {} })
|
||||
Glob({ pattern: `${sessionFolder}/discussions/discuss-*.md` }).forEach(f => { try { documents.discussions.push(Read(f)) } catch {} })
|
||||
|
||||
const docInventory = {
|
||||
config: !!documents.config, discoveryContext: !!documents.discoveryContext,
|
||||
productBrief: !!documents.productBrief, requirements: documents.requirements.length > 0,
|
||||
architecture: documents.adrs.length > 0, epics: documents.epics.length > 0,
|
||||
discussions: documents.discussions.length
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Review Execution (Branch by Mode)
|
||||
|
||||
**Code Review — 4-Dimension Analysis:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
const findings = { critical: [], high: [], medium: [], low: [] }
|
||||
|
||||
// Quality: @ts-ignore, any, console.log, empty catch
|
||||
const qualityIssues = reviewQuality(changedFiles)
|
||||
|
||||
// Security: eval/exec/innerHTML, hardcoded secrets, SQL injection, XSS
|
||||
const securityIssues = reviewSecurity(changedFiles)
|
||||
|
||||
// Architecture: circular deps, large files, layering violations
|
||||
const architectureIssues = reviewArchitecture(changedFiles, fileContents)
|
||||
|
||||
// Requirement Verification: plan acceptance criteria vs implementation
|
||||
const requirementIssues = plan ? verifyRequirements(plan, fileContents) : []
|
||||
|
||||
const allIssues = [...qualityIssues, ...securityIssues, ...architectureIssues, ...requirementIssues]
|
||||
allIssues.forEach(issue => findings[issue.severity].push(issue))
|
||||
|
||||
// Verdict determination
|
||||
const hasCritical = findings.critical.length > 0
|
||||
const verdict = hasCritical ? 'BLOCK' : findings.high.length > 3 ? 'CONDITIONAL' : 'APPROVE'
|
||||
}
|
||||
```
|
||||
|
||||
Review dimension functions:
|
||||
|
||||
```javascript
|
||||
function reviewQuality(files) {
|
||||
const issues = []
|
||||
const tsIgnore = Grep({ pattern: '@ts-ignore|@ts-expect-error', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (tsIgnore) issues.push({ type: 'quality', detail: '@ts-ignore/@ts-expect-error usage', severity: 'medium' })
|
||||
const anyType = Grep({ pattern: ': any[^A-Z]|as any', glob: '*.{ts,tsx}', output_mode: 'content' })
|
||||
if (anyType) issues.push({ type: 'quality', detail: 'Untyped `any` usage', severity: 'medium' })
|
||||
const consoleLogs = Grep({ pattern: 'console\\.log', glob: '*.{ts,tsx,js,jsx}', path: 'src/', output_mode: 'content' })
|
||||
if (consoleLogs) issues.push({ type: 'quality', detail: 'console.log in source code', severity: 'low' })
|
||||
const emptyCatch = Grep({ pattern: 'catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content', multiline: true })
|
||||
if (emptyCatch) issues.push({ type: 'quality', detail: 'Empty catch blocks', severity: 'high' })
|
||||
return issues
|
||||
}
|
||||
|
||||
function reviewSecurity(files) {
|
||||
const issues = []
|
||||
const dangerousFns = Grep({ pattern: '\\beval\\b|\\bexec\\b|innerHTML|dangerouslySetInnerHTML', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (dangerousFns) issues.push({ type: 'security', detail: 'Dangerous function: eval/exec/innerHTML', severity: 'critical' })
|
||||
const secrets = Grep({ pattern: 'password\\s*=\\s*["\']|secret\\s*=\\s*["\']|api_key\\s*=\\s*["\']', glob: '*.{ts,tsx,js,jsx,py}', output_mode: 'content', '-i': true })
|
||||
if (secrets) issues.push({ type: 'security', detail: 'Hardcoded secrets/passwords', severity: 'critical' })
|
||||
const sqlInjection = Grep({ pattern: 'query\\s*\\(\\s*`|execute\\s*\\(\\s*`', glob: '*.{ts,js,py}', output_mode: 'content', '-i': true })
|
||||
if (sqlInjection) issues.push({ type: 'security', detail: 'Potential SQL injection via template literals', severity: 'critical' })
|
||||
const xssRisk = Grep({ pattern: 'document\\.write|window\\.location\\s*=', glob: '*.{ts,tsx,js,jsx}', output_mode: 'content' })
|
||||
if (xssRisk) issues.push({ type: 'security', detail: 'Potential XSS vectors', severity: 'high' })
|
||||
return issues
|
||||
}
|
||||
|
||||
function reviewArchitecture(files, fileContents) {
|
||||
const issues = []
|
||||
for (const [file, content] of Object.entries(fileContents)) {
|
||||
const imports = content.match(/from\s+['"]([^'"]+)['"]/g) || []
|
||||
if (imports.filter(i => i.includes('../..')).length > 2) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: excessive parent imports (layering violation)`, severity: 'medium' })
|
||||
}
|
||||
if (content.split('\n').length > 500) {
|
||||
issues.push({ type: 'architecture', detail: `${file}: ${content.split('\n').length} lines - consider splitting`, severity: 'low' })
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
function verifyRequirements(plan, fileContents) {
|
||||
const issues = []
|
||||
for (const planTask of (plan.tasks || [])) {
|
||||
for (const criterion of (planTask.acceptance || [])) {
|
||||
const keywords = criterion.toLowerCase().split(/\s+/).filter(w => w.length > 4)
|
||||
const hasEvidence = keywords.some(kw => Object.values(fileContents).some(c => c.toLowerCase().includes(kw)))
|
||||
if (!hasEvidence) {
|
||||
issues.push({ type: 'requirement', detail: `Acceptance criterion may not be met: "${criterion}" (${planTask.title})`, severity: 'high' })
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality — 4-Dimension Scoring:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
const scores = { completeness: 0, consistency: 0, traceability: 0, depth: 0 }
|
||||
|
||||
// Completeness (25%): all sections present with content
|
||||
function scoreCompleteness(docs) {
|
||||
let score = 0
|
||||
const checks = [
|
||||
{ name: 'spec-config.json', present: !!docs.config, weight: 5 },
|
||||
{ name: 'discovery-context.json', present: !!docs.discoveryContext, weight: 10 },
|
||||
{ name: 'product-brief.md', present: !!docs.productBrief, weight: 20 },
|
||||
{ name: 'requirements/_index.md', present: !!docs.requirementsIndex, weight: 15 },
|
||||
{ name: 'REQ-* files', present: docs.requirements.length > 0, weight: 10 },
|
||||
{ name: 'architecture/_index.md', present: !!docs.architectureIndex, weight: 15 },
|
||||
{ name: 'ADR-* files', present: docs.adrs.length > 0, weight: 10 },
|
||||
{ name: 'epics/_index.md', present: !!docs.epicsIndex, weight: 10 },
|
||||
{ name: 'EPIC-* files', present: docs.epics.length > 0, weight: 5 }
|
||||
]
|
||||
checks.forEach(c => { if (c.present) score += c.weight })
|
||||
return { score, issues: checks.filter(c => !c.present).map(c => `Missing: ${c.name}`) }
|
||||
}
|
||||
|
||||
// Consistency (25%): terminology, format, references
|
||||
function scoreConsistency(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
const sessionId = docs.config?.session_id
|
||||
if (sessionId && docs.productBrief && !docs.productBrief.includes(sessionId)) {
|
||||
score -= 15; issues.push('Product Brief missing session_id reference')
|
||||
}
|
||||
const docsWithFM = [docs.productBrief, docs.requirementsIndex, docs.architectureIndex, docs.epicsIndex].filter(Boolean)
|
||||
const hasFM = docsWithFM.map(d => /^---\n[\s\S]+?\n---/.test(d))
|
||||
if (!hasFM.every(v => v === hasFM[0])) {
|
||||
score -= 20; issues.push('Inconsistent YAML frontmatter across documents')
|
||||
}
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
// Traceability (25%): goals → reqs → arch → stories chain
|
||||
function scoreTraceability(docs) {
|
||||
let score = 0
|
||||
const issues = []
|
||||
if (docs.productBrief && docs.requirementsIndex) {
|
||||
if (docs.requirements.some(r => /goal|brief|vision/i.test(r))) score += 25
|
||||
else issues.push('Requirements lack references to Product Brief goals')
|
||||
}
|
||||
if (docs.requirementsIndex && docs.architectureIndex) {
|
||||
if (docs.adrs.some(a => /REQ-|requirement/i.test(a))) score += 25
|
||||
else issues.push('Architecture ADRs lack requirement references')
|
||||
}
|
||||
if (docs.requirementsIndex && docs.epicsIndex) {
|
||||
if (docs.epics.some(e => /REQ-|requirement/i.test(e))) score += 25
|
||||
else issues.push('Epics/Stories lack requirement tracing')
|
||||
}
|
||||
if (score >= 50) score += 25
|
||||
return { score: Math.min(100, score), issues }
|
||||
}
|
||||
|
||||
// Depth (25%): AC testable, ADRs justified, stories estimable
|
||||
function scoreDepth(docs) {
|
||||
let score = 100
|
||||
const issues = []
|
||||
if (!docs.requirements.some(r => /acceptance|criteria|验收/i.test(r) && r.length > 200)) {
|
||||
score -= 25; issues.push('Acceptance criteria may lack specificity')
|
||||
}
|
||||
if (docs.adrs.length > 0 && !docs.adrs.some(a => /alternative|替代|pros|cons/i.test(a))) {
|
||||
score -= 25; issues.push('ADRs lack alternatives analysis')
|
||||
}
|
||||
if (docs.epics.length > 0 && !docs.epics.some(e => /\b[SMLX]{1,2}\b|Small|Medium|Large/.test(e))) {
|
||||
score -= 25; issues.push('Stories lack size estimates')
|
||||
}
|
||||
if (![docs.architectureIndex, docs.epicsIndex].some(d => d && /```mermaid/.test(d))) {
|
||||
score -= 10; issues.push('Missing Mermaid diagrams')
|
||||
}
|
||||
return { score: Math.max(0, score), issues }
|
||||
}
|
||||
|
||||
const completenessResult = scoreCompleteness(documents)
|
||||
const consistencyResult = scoreConsistency(documents)
|
||||
const traceabilityResult = scoreTraceability(documents)
|
||||
const depthResult = scoreDepth(documents)
|
||||
|
||||
scores.completeness = completenessResult.score
|
||||
scores.consistency = consistencyResult.score
|
||||
scores.traceability = traceabilityResult.score
|
||||
scores.depth = depthResult.score
|
||||
|
||||
const overallScore = (scores.completeness + scores.consistency + scores.traceability + scores.depth) / 4
|
||||
const qualityGate = overallScore >= 80 ? 'PASS' : overallScore >= 60 ? 'REVIEW' : 'FAIL'
|
||||
const allSpecIssues = [...completenessResult.issues, ...consistencyResult.issues, ...traceabilityResult.issues, ...depthResult.issues]
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Report Generation (Branch by Mode)
|
||||
|
||||
**Code Review — Generate Recommendations:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
const totalIssues = Object.values(findings).flat().length
|
||||
const recommendations = []
|
||||
if (hasCritical) recommendations.push('Fix all critical security issues before merging')
|
||||
if (findings.high.length > 0) recommendations.push('Address high severity issues in a follow-up')
|
||||
if (findings.medium.length > 3) recommendations.push('Consider refactoring to reduce medium severity issues')
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality — Generate Reports:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
// Generate readiness-report.md
|
||||
const readinessReport = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: readiness-report
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Readiness Report
|
||||
|
||||
## Quality Scores
|
||||
| Dimension | Score | Weight |
|
||||
|-----------|-------|--------|
|
||||
| Completeness | ${scores.completeness}% | 25% |
|
||||
| Consistency | ${scores.consistency}% | 25% |
|
||||
| Traceability | ${scores.traceability}% | 25% |
|
||||
| Depth | ${scores.depth}% | 25% |
|
||||
| **Overall** | **${overallScore.toFixed(1)}%** | **100%** |
|
||||
|
||||
## Quality Gate: ${qualityGate}
|
||||
|
||||
## Issues Found
|
||||
${allSpecIssues.map(i => '- ' + i).join('\n') || 'None'}
|
||||
|
||||
## Document Inventory
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (v === true ? '✓' : v === false ? '✗' : v)).join('\n')}
|
||||
`
|
||||
Write(`${sessionFolder}/readiness-report.md`, readinessReport)
|
||||
|
||||
// Generate spec-summary.md
|
||||
const specSummary = `---
|
||||
session_id: ${documents.config?.session_id || 'unknown'}
|
||||
phase: 6
|
||||
document_type: spec-summary
|
||||
status: complete
|
||||
generated_at: ${new Date().toISOString()}
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Specification Summary
|
||||
|
||||
**Topic**: ${documents.config?.topic || 'N/A'}
|
||||
**Complexity**: ${documents.config?.complexity || 'N/A'}
|
||||
**Quality Score**: ${overallScore.toFixed(1)}% (${qualityGate})
|
||||
**Discussion Rounds**: ${documents.discussions.length}
|
||||
|
||||
## Key Deliverables
|
||||
- Product Brief: ${docInventory.productBrief ? '✓' : '✗'}
|
||||
- Requirements (PRD): ${docInventory.requirements ? '✓ (' + documents.requirements.length + ' items)' : '✗'}
|
||||
- Architecture: ${docInventory.architecture ? '✓ (' + documents.adrs.length + ' ADRs)' : '✗'}
|
||||
- Epics & Stories: ${docInventory.epics ? '✓ (' + documents.epics.length + ' epics)' : '✗'}
|
||||
|
||||
## Next Steps
|
||||
${qualityGate === 'PASS' ? '- Ready for handoff to execution workflows' :
|
||||
qualityGate === 'REVIEW' ? '- Address review items, then proceed to execution' :
|
||||
'- Fix critical issues before proceeding'}
|
||||
`
|
||||
Write(`${sessionFolder}/spec-summary.md`, specSummary)
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Report to Coordinator (Branch by Mode)
|
||||
|
||||
**Code Review Report:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'code') {
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "reviewer", to: "coordinator",
|
||||
type: hasCritical ? "fix_required" : "review_result",
|
||||
summary: `REVIEW ${verdict}: ${totalIssues}个发现 (critical=${findings.critical.length}, high=${findings.high.length})`,
|
||||
data: { verdict, critical: findings.critical.length, high: findings.high.length, medium: findings.medium.length, low: findings.low.length }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## Code Review Report
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**Verdict**: ${verdict}
|
||||
**Files Reviewed**: ${changedFiles.length}
|
||||
**Total Findings**: ${totalIssues}
|
||||
|
||||
### Finding Summary
|
||||
- Critical: ${findings.critical.length}
|
||||
- High: ${findings.high.length}
|
||||
- Medium: ${findings.medium.length}
|
||||
- Low: ${findings.low.length}
|
||||
|
||||
${findings.critical.length > 0 ? '### Critical Issues\n' + findings.critical.map(f => '- [' + f.type.toUpperCase() + '] ' + f.detail).join('\n') + '\n' : ''}
|
||||
${findings.high.length > 0 ? '### High Severity\n' + findings.high.map(f => '- [' + f.type.toUpperCase() + '] ' + f.detail).join('\n') + '\n' : ''}
|
||||
### Recommendations
|
||||
${recommendations.map(r => '- ' + r).join('\n')}
|
||||
|
||||
${plan ? '### Requirement Verification\n' + (plan.tasks || []).map(t => '- **' + t.title + '**: ' + (requirementIssues.filter(i => i.detail.includes(t.title)).length === 0 ? 'Met' : 'Needs verification')).join('\n') : ''}`,
|
||||
summary: `Review: ${verdict} (${totalIssues} findings)`
|
||||
})
|
||||
|
||||
if (!hasCritical) {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
}
|
||||
// If critical, keep in_progress for coordinator to create fix tasks
|
||||
}
|
||||
```
|
||||
|
||||
**Spec Quality Report:**
|
||||
|
||||
```javascript
|
||||
if (reviewMode === 'spec') {
|
||||
mcp__ccw-tools__team_msg({
|
||||
operation: "log", team: teamName,
|
||||
from: "reviewer", to: "coordinator",
|
||||
type: qualityGate === 'FAIL' ? "fix_required" : "quality_result",
|
||||
summary: `质量检查 ${qualityGate}: ${overallScore.toFixed(1)}分 (完整性${scores.completeness}/一致性${scores.consistency}/追溯${scores.traceability}/深度${scores.depth})`,
|
||||
data: { gate: qualityGate, score: overallScore, issues: allSpecIssues }
|
||||
})
|
||||
|
||||
SendMessage({
|
||||
type: "message",
|
||||
recipient: "coordinator",
|
||||
content: `## 质量审查报告
|
||||
|
||||
**Task**: ${task.subject}
|
||||
**总分**: ${overallScore.toFixed(1)}%
|
||||
**Gate**: ${qualityGate}
|
||||
|
||||
### 评分详情
|
||||
| 维度 | 分数 |
|
||||
|------|------|
|
||||
| 完整性 | ${scores.completeness}% |
|
||||
| 一致性 | ${scores.consistency}% |
|
||||
| 可追溯性 | ${scores.traceability}% |
|
||||
| 深度 | ${scores.depth}% |
|
||||
|
||||
### 问题列表 (${allSpecIssues.length})
|
||||
${allSpecIssues.map(i => '- ' + i).join('\n') || '无问题'}
|
||||
|
||||
### 文档清单
|
||||
${Object.entries(docInventory).map(([k, v]) => '- ' + k + ': ' + (typeof v === 'boolean' ? (v ? '✓' : '✗') : v)).join('\n')}
|
||||
|
||||
### 输出位置
|
||||
- 就绪报告: ${sessionFolder}/readiness-report.md
|
||||
- 执行摘要: ${sessionFolder}/spec-summary.md
|
||||
|
||||
${qualityGate === 'PASS' ? '质量达标,可进入最终讨论轮次 DISCUSS-006。' :
|
||||
qualityGate === 'REVIEW' ? '质量基本达标但有改进空间,建议在讨论中审查。' :
|
||||
'质量未达标,建议创建 DRAFT-fix 任务修复关键问题。'}`,
|
||||
summary: `质量 ${qualityGate}: ${overallScore.toFixed(1)}分`
|
||||
})
|
||||
|
||||
if (qualityGate !== 'FAIL') {
|
||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||
}
|
||||
}
|
||||
|
||||
// Check for next REVIEW-* or QUALITY-* task → back to Phase 1
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Resolution |
|
||||
|----------|------------|
|
||||
| No REVIEW-*/QUALITY-* tasks available | Idle, wait for coordinator assignment |
|
||||
| Plan file not found (code review) | Review without requirement verification, note in report |
|
||||
| No changed files detected | Report to coordinator, may need manual file list |
|
||||
| Documents missing (spec quality) | Score as 0 for completeness, report to coordinator |
|
||||
| Cannot parse YAML frontmatter | Skip consistency check for that document |
|
||||
| Grep pattern errors | Skip specific check, continue with remaining |
|
||||
| CLI analysis timeout | Report partial results, note incomplete analysis |
|
||||
| Session folder not found | Notify coordinator, request session path |
|
||||
| Unexpected error | Log error via team_msg, report to coordinator |
|
||||
Reference in New Issue
Block a user