Files
AirCoding/packages/workers/src/roles/ReviewerRole.ts
AirCoding 5e282a39b4 feat(round2+round3): 完整实现 A/B/C/D 主线 + round3-F/H 修复
Round2 主线:
- A: 事件落库地基 (RuntimeApp EventStore 单例 + 14 repo wiring)
- B: 执行体对齐 (read-before-edit, verification-before-completion)
- C: 界面对齐 (@opentui/solid, 删除 runtime 依赖)
- D: 经验闭环 (ExperienceMiner, DebuggerRole, CompactorRole)

Round2 补充修复:
- fail-on-missing 反作弊门禁
- projection-store-apply.test.ts 补写
- 3个空壳测试转行为 (evidence-store, recovery-impl, knowledge-store)
- ask 项目根支持 AIRCODING_PROJECT_ROOT
- Worker 事件契约修复 (task.attempt.started → checkpoint)

Round3-F: cpp 工具切换
- 删除 BuiltInToolRegistrar cpp.* 闭包
- 接入 toolchain-cpp 真实 CppToolRegistrar
- canonical envelope {status/output/metadata}
- ExecutorRole system prompt 对齐新工具名

Round3-H: Doctor 5 类报告
- toolchain (cmake/ninja/cppcheck/clangd/g++)
- display (X11/Wayland + ImageMagick)
- network (internet connectivity)
- provider (api_key/base_url/model/connectivity)

Secret 脱敏:
- 状态交接.md: sk- → \${OPENAI_API_KEY}
- .gitignore: 添加 .air/ .claude/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:13:16 +08:00

116 lines
4.0 KiB
TypeScript
Executable File

/**
* ReviewerRole - Code review worker
* Read-only, reviews code changes for correctness and compliance.
* DD §8.4.
*
* @module packages/workers/src/roles/ReviewerRole
*/
import { WorkerRuntime } from '../WorkerRuntime.js'
export interface ReviewerResult {
status: 'pass' | 'fail' | 'needs_work' | 'blocked'
findings: Array<{
severity: 'info' | 'warning' | 'error' | 'fatal'
file?: string
line?: number
message: string
suggestion?: string
}>
summary: string
}
export class ReviewerRole {
private runtime: WorkerRuntime
constructor(runtime: WorkerRuntime) {
this.runtime = runtime
}
async run(review_spec: { task_id: string; change_files: string[] }): Promise<ReviewerResult> {
const result: ReviewerResult = { status: 'pass', findings: [], summary: '' }
try {
this.runtime.checkpoint('review_started', { task_id: review_spec.task_id })
for (const file of review_spec.change_files) {
try {
// Read each changed file
const read_result = await this.runtime.call_tool('fs.read', { path: file })
if (read_result.type === 'error') {
result.findings.push({ severity: 'warning', file, message: `Cannot read file: ${file}` })
continue
}
// Get git diff
const diff_result = await this.runtime.call_tool('git.diff', { path: file, staged: false })
// Check for common issues
const content = typeof read_result.content === 'string' ? read_result.content :
(read_result.content as any)?.content || JSON.stringify(read_result.content)
// Check for execSync usage (security audit)
if (content.includes('execSync')) {
result.findings.push({
severity: 'error',
file,
message: 'Found execSync usage. Use execFileSync with args array for command injection prevention.',
suggestion: 'Replace with execFileSync(cmd, args, opts)'
})
}
// Check for hardcoded credentials
if (/api_key|password|secret|token\s*[:=]\s*['"][^'"]+['"]/i.test(content)) {
result.findings.push({
severity: 'error',
file,
message: 'Possible hardcoded credential detected',
suggestion: 'Use environment variables or config files for credentials'
})
}
// Check for direct import violations (INV-4)
if (file.includes('tui/src/') && content.includes("from '@aircoding/runtime'")) {
result.findings.push({
severity: 'fatal',
file,
message: 'INV-4 violation: TUI must not import from runtime',
suggestion: 'Use contracts package or duplicate the ProjectionClient contract locally'
})
}
// Successful inspection with no issues
if (result.findings.filter(f => f.file === file).length === 0) {
result.findings.push({
severity: 'info',
file,
message: 'File reviewed — no issues found'
})
}
} catch (e: any) {
result.findings.push({
severity: 'warning',
file,
message: `Review error on ${file}: ${e.message}`
})
}
}
// Determine overall status
const has_fatal = result.findings.some(f => f.severity === 'fatal')
const has_error = result.findings.some(f => f.severity === 'error')
if (has_fatal) result.status = 'fail'
else if (has_error) result.status = 'needs_work'
result.summary = `Reviewed ${review_spec.change_files.length} files: ${result.findings.length} findings`
this.runtime.checkpoint('review_completed', { task_id: review_spec.task_id })
return result
} catch (error) {
result.status = 'blocked'
result.findings.push({ severity: 'fatal', message: error instanceof Error ? error.message : String(error) })
return result
}
}
}