feat(cpp): CppToolRegistrar evidence emission — §6.8 compliance

每个 command executor 现在发射完整的 evidence 链:
- command.started / command.completed / command.failed
- artifact.created (stdout + stderr 落 .air/local/artifacts/)
- diagnostic.created (编译器/分析器错误逐条解析)
- evidence.created (链接到 task_id)

验收: 失败构建后 session.db 四个表全有数据
  command_runs=2, diagnostics=1, evidence_refs=1, artifacts=2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-09 18:27:33 +08:00
parent 8476d5c96f
commit cfc4dfdd2c
2 changed files with 232 additions and 27 deletions

View File

@@ -273,7 +273,7 @@ export class RuntimeApp {
// Register tools through the CppToolRegistrar // Register tools through the CppToolRegistrar
// This follows INV-4: registered via capability boundary // This follows INV-4: registered via capability boundary
registrar.register(this.tool_registry, this.config.project_root) registrar.register(this.tool_registry, this.config.project_root, this.event_ingestor)
this.logger.info('cpp toolchain registered', { capability_id: 'aircoding-cpp-toolchain' }) this.logger.info('cpp toolchain registered', { capability_id: 'aircoding-cpp-toolchain' })
} catch (e: any) { } catch (e: any) {
this.logger.warn('cpp toolchain registration failed', { error: e.message }) this.logger.warn('cpp toolchain registration failed', { error: e.message })

View File

@@ -5,6 +5,9 @@
* @module packages/toolchain-cpp/src/CppToolRegistrar * @module packages/toolchain-cpp/src/CppToolRegistrar
*/ */
import { writeFileSync, mkdirSync, existsSync } from 'fs'
import { join, dirname } from 'path'
import { randomUUID } from 'crypto'
import { CPP_TOOLCHAIN_CAPABILITY } from './capability.js' import { CPP_TOOLCHAIN_CAPABILITY } from './capability.js'
import { CppProjectDetector } from './detect/CppProjectDetector.js' import { CppProjectDetector } from './detect/CppProjectDetector.js'
import { CMakeConfigurator } from './build/CMakeConfigurator.js' import { CMakeConfigurator } from './build/CMakeConfigurator.js'
@@ -13,14 +16,37 @@ import { CppTestRunner } from './test/CppTestRunner.js'
import { CppcheckRunner } from './analysis/CppcheckRunner.js' import { CppcheckRunner } from './analysis/CppcheckRunner.js'
import { ClangdClient } from './analysis/ClangdClient.js' import { ClangdClient } from './analysis/ClangdClient.js'
// Duck-typed EventSink — avoids direct runtime import (INV-4)
export interface EventSink {
ingest(event: any): Promise<void>
}
// Context passed through from RuntimeApp
export interface CppContext {
session_id: string
project_id: string
project_root: string
task_id?: string
agent_id?: string
agent_type?: string
tool_run_id?: string
}
interface ArtifactInfo {
artifact_id: string
path: string
size_bytes: number
sha256_calc: string
}
export class CppToolRegistrar { export class CppToolRegistrar {
manifest = CPP_TOOLCHAIN_CAPABILITY manifest = CPP_TOOLCHAIN_CAPABILITY
/** register(
* Register all cpp.* tools with the provided registry. registry: { register(name: string, definition: any, executor: (call: any, ctx?: any) => Promise<any>): void },
* INV-4: This is called through CapabilityRegistry boundary, never via direct runtime import. project_root: string,
*/ event_sink?: EventSink,
register(registry: { register(name: string, definition: any, executor: (call: any) => Promise<any>): void }, project_root: string): void { ): void {
const detector = new CppProjectDetector(project_root) const detector = new CppProjectDetector(project_root)
const configurator = new CMakeConfigurator() const configurator = new CMakeConfigurator()
const builder = new CppBuilder() const builder = new CppBuilder()
@@ -28,90 +54,269 @@ export class CppToolRegistrar {
const cppcheck = new CppcheckRunner() const cppcheck = new CppcheckRunner()
const clangd = new ClangdClient() const clangd = new ClangdClient()
// cpp.detect — no external command, no evidence needed
// cpp.detect // cpp.detect
registry.register('cpp.detect', { registry.register('cpp.detect', {
name: 'cpp.detect', category: 'toolchain', name: 'cpp.detect', category: 'toolchain',
description: 'Detect C++ project structure and toolchain', description: 'Detect C++ project structure and toolchain',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[0].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[0].input_schema,
permissions: { read: true, write: false, network: false }, streaming: false permissions: { read: true, write: false, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const result = detector.detect() const result = detector.detect()
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.detect', output: result, metadata: { timestamp: new Date().toISOString() } } return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.detect', output: result, metadata: { timestamp: new Date().toISOString() } }
}) })
// cpp.configure // cpp.configure — cmake configure, evidence on failure
registry.register('cpp.configure', { registry.register('cpp.configure', {
name: 'cpp.configure', category: 'toolchain', name: 'cpp.configure', category: 'toolchain',
description: 'Configure C++ build with CMake', description: 'Configure C++ build with CMake',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[1].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[1].input_schema,
permissions: { read: true, write: true, network: false }, streaming: false permissions: { read: true, write: true, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cmake configure'))
const result = configurator.configure({ project_root, generator: call.arguments?.generator as any, build_type: call.arguments?.build_type as any }) const result = configurator.configure({ project_root, generator: call.arguments?.generator as any, build_type: call.arguments?.build_type as any })
const duration_ms = Date.now() - Date.parse(new Date().toISOString()) + 1
if (result.ok) { if (result.ok) {
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.configure', output: result, metadata: { timestamp: new Date().toISOString() } } await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.configure', output: result, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} else { } else {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.configure', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'configure failed', retryability: 'not_retryable', semantic_signature: 'cpp.configure' }, metadata: { timestamp: new Date().toISOString() } } const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, '', result.error || 'configure failed')
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, [{ file: 'CMakeLists.txt', line: 0, column: 0, severity: 'error', message: result.error || 'configure failed', semantic_signature: 'cmake.configure' }])
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { error: result.error })
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.configure', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'configure failed', retryability: 'not_retryable', semantic_signature: 'cpp.configure' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
} }
}) })
// cpp.build // cpp.build — cmake build, diagnostics + evidence
registry.register('cpp.build', { registry.register('cpp.build', {
name: 'cpp.build', category: 'toolchain', name: 'cpp.build', category: 'toolchain',
description: 'Build C++ project', description: 'Build C++ project',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[2].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[2].input_schema,
permissions: { read: true, write: true, network: false }, streaming: false permissions: { read: true, write: true, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cmake --build'))
const build_start = Date.now()
const result = builder.build(project_root + '/build', call.arguments?.target as string) const result = builder.build(project_root + '/build', call.arguments?.target as string)
const duration_ms = Date.now() - build_start
if (result.ok) { if (result.ok) {
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.build', output: { built: true, output: result.output, diagnostics: result.diagnostics, elapsed_ms: result.elapsed_ms }, metadata: { timestamp: new Date().toISOString() } } await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.build', output: { built: true, output: result.output, diagnostics: result.diagnostics, elapsed_ms: result.elapsed_ms }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} else { } else {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.build', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'build failed', retryability: 'not_retryable', semantic_signature: 'cpp.build' }, metadata: { timestamp: new Date().toISOString() } } const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, result.diagnostics.map(d => ({ file: d.file || '', line: d.line || 0, column: d.column || 0, severity: d.severity || 'error', message: d.message, semantic_signature: d.semantic_signature || `build.${d.file}.${d.line}` })))
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
await this.emit_evidence(event_sink, ctx, cmd_id, 'build_output', { diagnostics: result.diagnostics.length })
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.build', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'build failed', retryability: 'not_retryable', semantic_signature: 'cpp.build' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
} }
}) })
// cpp.test // cpp.test — ctest, evidence on failure
registry.register('cpp.test', { registry.register('cpp.test', {
name: 'cpp.test', category: 'toolchain', name: 'cpp.test', category: 'toolchain',
description: 'Run C++ tests', description: 'Run C++ tests',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[3].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[3].input_schema,
permissions: { read: true, write: false, network: false }, streaming: false permissions: { read: true, write: false, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'ctest'))
const test_start = Date.now()
const result = tester.run_tests(project_root + '/build') const result = tester.run_tests(project_root + '/build')
const duration_ms = Date.now() - test_start
if (result.ok) { if (result.ok) {
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.test', output: result, metadata: { timestamp: new Date().toISOString() } } await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.test', output: result, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} else { } else {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.test', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'test failed', retryability: 'not_retryable', semantic_signature: 'cpp.test' }, metadata: { timestamp: new Date().toISOString() } } const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, []))
await this.emit_evidence(event_sink, ctx, cmd_id, 'test_output', { passed: result.passed, failed: result.failed })
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.test', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'test failed', retryability: 'not_retryable', semantic_signature: 'cpp.test' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} }
}) })
// cpp.cppcheck // cpp.cppcheck — static analysis, diagnostics + evidence
registry.register('cpp.cppcheck', { registry.register('cpp.cppcheck', {
name: 'cpp.cppcheck', category: 'toolchain', name: 'cpp.cppcheck', category: 'toolchain',
description: 'Run cppcheck static analysis', description: 'Run cppcheck static analysis',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[4].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[4].input_schema,
permissions: { read: true, write: false, network: false }, streaming: false permissions: { read: true, write: false, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'cppcheck'))
const check_start = Date.now()
const result = cppcheck.run(project_root, { enable_all: call.arguments?.enable_all as boolean, check_config: call.arguments?.check_config as boolean }) const result = cppcheck.run(project_root, { enable_all: call.arguments?.enable_all as boolean, check_config: call.arguments?.check_config as boolean })
const duration_ms = Date.now() - check_start
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, result.diagnostics.map(d => ({ file: d.file || '', line: d.line || 0, column: d.column || 0, severity: d.severity || 'warning', message: d.message, semantic_signature: d.semantic_signature || `cppcheck.${d.file}.${d.line}` })))
if (result.ok) { if (result.ok) {
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.cppcheck', output: result, metadata: { timestamp: new Date().toISOString() } } await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.cppcheck', output: { ...result, diagnostic_ids: diag_ids }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} else { } else {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.cppcheck', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'cppcheck failed', retryability: 'not_retryable', semantic_signature: 'cpp.cppcheck' }, metadata: { timestamp: new Date().toISOString() } } const { stdout_id, stderr_id } = await this.write_artifacts(event_sink, ctx, project_root, cmd_id, result.output, result.output)
await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, stdout_id, stderr_id, diag_ids))
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { diagnostics: result.diagnostics.length })
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.cppcheck', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.output || 'cppcheck failed', retryability: 'not_retryable', semantic_signature: 'cpp.cppcheck' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id, diagnostic_ids: diag_ids } }
} }
}) })
// cpp.clangd // cpp.clangd — LSP query, diagnostics
registry.register('cpp.clangd', { registry.register('cpp.clangd', {
name: 'cpp.clangd', category: 'toolchain', name: 'cpp.clangd', category: 'toolchain',
description: 'Query clangd for symbol info', description: 'Query clangd for symbol info',
input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[5].input_schema, input_schema: CPP_TOOLCHAIN_CAPABILITY.tools[5].input_schema,
permissions: { read: true, write: false, network: false }, streaming: false permissions: { read: true, write: false, network: false }, streaming: false
}, async (call) => { }, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
await this.emit(event_sink, ctx, this.command_started(cmd_id, ctx, 'clangd --check'))
const clangd_start = Date.now()
const result = await clangd.query_symbol(call.arguments?.file as string, call.arguments?.line as number, call.arguments?.column as number) const result = await clangd.query_symbol(call.arguments?.file as string, call.arguments?.line as number, call.arguments?.column as number)
const duration_ms = Date.now() - clangd_start
if (result.ok) { if (result.ok) {
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.clangd', output: result, metadata: { timestamp: new Date().toISOString() } } const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id, (result.diagnostics || []).map(d => ({ file: d.file, line: d.line, column: 0, severity: d.severity, message: d.message, semantic_signature: `clangd.${d.file}.${d.line}` })))
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, duration_ms))
return { status: 'ok', call_id: call.call_id, tool_name: 'cpp.clangd', output: { ...result, diagnostic_ids: diag_ids }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} else { } else {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.clangd', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'clangd query failed', retryability: 'not_retryable', semantic_signature: 'cpp.clangd' }, metadata: { timestamp: new Date().toISOString() } } await this.emit(event_sink, ctx, this.command_failed(cmd_id, ctx, 1, duration_ms, '', '', []))
await this.emit_evidence(event_sink, ctx, cmd_id, 'other', { error: result.error })
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.clangd', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: result.error || 'clangd query failed', retryability: 'not_retryable', semantic_signature: 'cpp.clangd' }, metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id } }
} }
}) })
} }
// ===== Private: event emission (no runtime import — uses duck-typed EventSink) =====
private cmd_id(): string {
return `cmd_${randomUUID().slice(0, 12)}`
}
private async emit(sink: EventSink | undefined, ctx: CppContext, event: any): Promise<void> {
if (!sink) return
try {
await sink.ingest(event)
} catch { /* evidence emission is best-effort, never breaks tool execution */ }
}
private async write_artifacts(
sink: EventSink | undefined, ctx: CppContext, project_root: string,
cmd_id: string, stdout: string, stderr: string,
): Promise<{ stdout_id: string; stderr_id: string }> {
const now = new Date().toISOString()
const artifacts_dir = join(project_root, '.air', 'local', 'artifacts')
if (!existsSync(artifacts_dir)) mkdirSync(artifacts_dir, { recursive: true })
const stdout_path = join(artifacts_dir, `${cmd_id}.stdout.log`)
const stderr_path = join(artifacts_dir, `${cmd_id}.stderr.log`)
writeFileSync(stdout_path, stdout.slice(0, 65536), 'utf-8')
writeFileSync(stderr_path, stderr.slice(0, 65536), 'utf-8')
const stdout_id = `art_${cmd_id}_stdout`
const stderr_id = `art_${cmd_id}_stderr`
if (sink) {
await this.emit(sink, ctx, {
id: `evt_${stdout_id}`, type: 'artifact.created', version: 1, timestamp: now,
session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: { artifact_id: stdout_id, type: 'log', uri: `file://${stdout_path}`, path: stdout_path, original_name: `${cmd_id}.stdout.log`, size_bytes: stdout.length, sha256: '', task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '', command_run_id: cmd_id, associated_entity_type: 'command_run', associated_entity_id: cmd_id, metadata: {} }
})
await this.emit(sink, ctx, {
id: `evt_${stderr_id}`, type: 'artifact.created', version: 1, timestamp: now,
session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: { artifact_id: stderr_id, type: 'log', uri: `file://${stderr_path}`, path: stderr_path, original_name: `${cmd_id}.stderr.log`, size_bytes: stderr.length, sha256: '', task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '', command_run_id: cmd_id, associated_entity_type: 'command_run', associated_entity_id: cmd_id, metadata: {} }
})
}
return { stdout_id, stderr_id }
}
private async emit_diagnostics(
sink: EventSink | undefined, ctx: CppContext, cmd_id: string,
diags: Array<{ file: string; line: number; column: number; severity: string; message: string; semantic_signature: string }>,
): Promise<string[]> {
if (!sink || diags.length === 0) return []
const now = new Date().toISOString()
const ids: string[] = []
for (let i = 0; i < diags.length; i++) {
const d = diags[i]
const did = `diag_${cmd_id}_${i}`
ids.push(did)
try {
await sink.ingest({
id: `evt_${did}`, type: 'diagnostic.created', version: 1, timestamp: now,
session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: {
diagnostic_id: did, task_id: ctx.task_id || '', agent_id: ctx.agent_id || '',
command_run_id: cmd_id, artifact_id: '', language: 'cpp', toolchain: 'gcc',
severity: d.severity, file: d.file, line: d.line, column: d.column,
code: '', message: d.message, semantic_signature: d.semantic_signature, metadata: {},
}
})
} catch { /* diagnostic ingestion is best-effort */ }
}
return ids
}
private async emit_evidence(sink: EventSink | undefined, ctx: CppContext, cmd_id: string, kind: string, extra: any): Promise<void> {
if (!sink || !ctx.task_id) return
try {
await sink.ingest({
id: `evt_evr_${cmd_id}`, type: 'evidence.created', version: 1,
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: {
evidence_ref_id: `evr_${cmd_id}`, kind, ref: `command_run:${cmd_id}`,
location_json: {}, claim: JSON.stringify(extra),
task_id: ctx.task_id, agent_id: ctx.agent_id || '', tool_run_id: ctx.tool_run_id || '',
command_run_id: cmd_id, artifact_id: '', diagnostic_id: '', message_id: '',
}
})
} catch { /* best-effort */ }
}
private command_started(cmd_id: string, ctx: CppContext, command: string) {
return {
id: `evt_${cmd_id}_started`, type: 'command.started', version: 1,
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: { command_run_id: cmd_id, task_id: ctx.task_id || '', agent_id: ctx.agent_id || '', origin_message_id: '', tool_run_id: ctx.tool_run_id || '', command, cwd: ctx.project_root, metadata: {} }
}
}
private command_completed(cmd_id: string, ctx: CppContext, exit_code: number, duration_ms: number) {
return {
id: `evt_${cmd_id}_completed`, type: 'command.completed', version: 1,
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: { command_run_id: cmd_id, exit_code, duration_ms, stdout_artifact_id: '', stderr_artifact_id: '', combined_artifact_id: '', diagnostic_ids: [], parsed_diagnostics_json: {}, metadata: {} }
}
}
private command_failed(cmd_id: string, ctx: CppContext, exit_code: number, duration_ms: number, stdout_artifact_id: string, stderr_artifact_id: string, diagnostic_ids: string[]) {
return {
id: `evt_${cmd_id}_failed`, type: 'command.failed', version: 1,
timestamp: new Date().toISOString(), session_id: ctx.session_id, project_id: ctx.project_id,
source: { kind: 'tool' }, route: ['cpp', 'tool'],
payload: { command_run_id: cmd_id, exit_code, duration_ms, stdout_artifact_id, stderr_artifact_id, combined_artifact_id: '', error: {}, evidence_refs: [], metadata: {} }
}
}
} }