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>
This commit is contained in:
AirCoding
2026-06-09 16:13:16 +08:00
parent e383d5f6a7
commit 5e282a39b4
44 changed files with 2905 additions and 1343 deletions

View File

@@ -132,20 +132,6 @@ export class BuiltInToolRegistrar {
'project.profile.write': def('project.profile.write', 'project', 'Write language profile/toolchain configuration',
{ language: { type: 'string', description: 'Language (cpp/c/rust/python)' }, profile_json: { type: 'object', description: 'Profile configuration' } }, ['language', 'profile_json'],
{ read: false, write: true, network: false }),
// cpp toolchain
'cpp.detect': def('cpp.detect', 'debug', 'Detect C++ project structure, toolchain, and source files',
{ project_root: { type: 'string', description: 'Project root path' } }, []),
'cpp.cmake.configure': def('cpp.cmake.configure', 'build', 'Configure C++ build with CMake (Ninja preferred, Make fallback)',
{ generator: { type: 'string', description: 'Generator (Ninja/Unix Makefiles)' }, build_type: { type: 'string', description: 'Debug/Release/RelWithDebInfo' } }, [],
{ read: true, write: true, network: false }),
'cpp.build': def('cpp.build', 'build', 'Build C++ project via CMake',
{ target: { type: 'string', description: 'Build target' }, config: { type: 'string', description: 'Debug/Release' } }, []),
'cpp.test': def('cpp.test', 'test', 'Run C++ tests via ctest',
{ filter: { type: 'string', description: 'Test filter pattern' } }, []),
'cpp.static.cppcheck': def('cpp.static.cppcheck', 'static_analysis', 'Run cppcheck static analysis on C++ code',
{ path: { type: 'string', description: 'Path to analyze' }, severity: { type: 'string', description: 'Minimum severity' } }, []),
'cpp.clangd.query': def('cpp.clangd.query', 'static_analysis', 'Query clangd LSP for symbol definition or diagnostics',
{ file: { type: 'string', description: 'Source file path' }, line: { type: 'number', description: 'Line number' }, column: { type: 'number', description: 'Column number' } }, ['file']),
// debug
'debug.run': def('debug.run', 'debug', 'Run debugger on a target process or binary',
{ target: { type: 'string', description: 'Binary or process to debug' }, breakpoints: { type: 'array', items: { type: 'string' } } }, ['target']),
@@ -257,88 +243,6 @@ export class BuiltInToolRegistrar {
}
},
'cpp.detect': async (call: any) => {
try {
const root = (call.arguments as any)?.project_root || project_root
const cmake = existsSync(join(root, 'CMakeLists.txt'))
const makefile = existsSync(join(root, 'Makefile'))
return { status: "ok", call_id: call.call_id, tool_name, type: 'text',
output: { has_cmake: cmake, has_makefile: makefile, build_system: cmake ? 'cmake' : makefile ? 'make' : 'none' },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'cpp.cmake.configure': async (call: any) => {
try {
const { generator = 'Ninja', build_type = 'Debug' } = (call.arguments || {}) as any
const buildDir = join(project_root, 'build')
if (!existsSync(buildDir)) mkdirSync(buildDir, { recursive: true })
execFileSync('cmake', ['-G', generator, '-DCMAKE_BUILD_TYPE=' + build_type, '..'], { cwd: buildDir, stdio: 'pipe' })
return { status: "ok", call_id: call.call_id, tool_name, type: 'text', output: { generator, build_type, configured: true },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'cpp.build': async (call: any) => {
try {
const { target, config = 'Debug' } = (call.arguments || {}) as any
const args = target ? ['--build', '.', '--config', config, '--target', target] : ['--build', '.', '--config', config]
const out = execFileSync('cmake', args, { cwd: join(project_root, 'build'), stdio: 'pipe', timeout: 300000 })
return { status: "ok", call_id: call.call_id, tool_name, type: 'text',
output: { built: true, output: out.toString().slice(-500) },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'cpp.test': async (call: any) => {
try {
const { filter } = (call.arguments || {}) as any
const args = filter ? ['--output-on-failure', '-R', filter] : ['--output-on-failure']
const out = execFileSync('ctest', args, { cwd: join(project_root, 'build'), stdio: 'pipe', timeout: 300000 })
return { status: "ok", call_id: call.call_id, tool_name, type: 'text',
output: { passed: true, output: out.toString().slice(-1000) },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'cpp.static.cppcheck': async (call: any) => {
try {
const { path = 'src' } = (call.arguments || {}) as any
const out = execFileSync('cppcheck', ['--enable=all', '--quiet', path], { cwd: project_root, stdio: 'pipe', encoding: 'utf-8', timeout: 120000 })
return { status: "ok", call_id: call.call_id, tool_name, type: 'text',
output: { output: out.toString().slice(-500), issues_found: 0 },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'cpp.clangd.query': async (call: any) => {
try {
const { file, line = 0, column = 0 } = (call.arguments || {}) as any
const out = execFileSync('clangd', ['--check=' + file], { cwd: project_root, stdio: 'pipe', encoding: 'utf-8', timeout: 30000 })
return { status: "ok", call_id: call.call_id, tool_name, type: 'text',
output: { file, line, column, diagnostics: out.toString().slice(-1000) },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'ok' } }
} catch (e: any) {
return { status: 'error', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message, retryability: 'not_retryable', semantic_signature: tool_name },
metadata: { timestamp: new Date().toISOString(), call_id: call.call_id, tool_name, type: 'error' } }
}
},
'debug.run': async (call: any) => {
try {
const { target } = (call.arguments || {}) as any