chore: push all design docs, V2 plan specs, and current working state

Includes AirPlan design documents, AircOding-alpha1-plan, AirPlanV2,
AirPlan-ParaV2, AirPlan-Para V1 reference docs, and all working code
changes across packages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
AirCoding
2026-06-12 17:12:29 +08:00
parent 8f55c962bb
commit ae44be31d5
364 changed files with 46779 additions and 2812 deletions

View File

@@ -5,9 +5,10 @@
* @module packages/toolchain-cpp/src/CppToolRegistrar
*/
import { writeFileSync, mkdirSync, existsSync } from 'fs'
import { writeFileSync, mkdirSync, existsSync, readFileSync, statSync } from 'fs'
import { join, dirname } from 'path'
import { randomUUID } from 'crypto'
import { execFileSync } from 'child_process'
import { CPP_TOOLCHAIN_CAPABILITY } from './capability.js'
import { CppProjectDetector } from './detect/CppProjectDetector.js'
import { CMakeConfigurator } from './build/CMakeConfigurator.js'
@@ -198,6 +199,218 @@ export class CppToolRegistrar {
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 } }
}
})
// FR-017: cpp.debug — Run debug session or parse crash logs
registry.register('cpp.debug', {
name: 'cpp.debug',
category: 'toolchain',
description: 'Debug a C++ program or parse crash logs',
input_schema: {
type: 'object',
properties: {
target: { type: 'string', description: 'Binary or process to debug' },
source_file: { type: 'string', description: 'Source file for breakpoint' },
log_path: { type: 'string', description: 'Path to crash log to parse' },
format: { type: 'string', enum: ['gdb', 'lldb', 'valgrind', 'asan'], default: 'gdb', description: 'Log format' }
},
required: []
},
permissions: { read: true, write: false, network: false },
streaming: false
}, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
const { target, source_file, log_path, format } = call.arguments as any
try {
// If log_path is provided, parse the log
if (log_path) {
const logContent = readFileSync(join(project_root, log_path), 'utf-8')
const errors = logContent.split('\n')
.filter((l: string) => /error|fail|segfault|assert|abort|exception/i.test(l))
.slice(0, 50)
// Parse GDB/LLDB style backtrace if present
const backtrace: string[] = []
let inBacktrace = false
for (const line of logContent.split('\n')) {
if (line.includes('#0') || line.includes('#1') || line.includes('#2')) {
inBacktrace = true
backtrace.push(line.trim())
} else if (inBacktrace && !line.includes('#')) {
inBacktrace = false
}
}
await this.emit_evidence(event_sink, ctx, cmd_id, 'crash_log', { error_count: errors.length })
return {
status: 'ok',
call_id: call.call_id,
tool_name: 'cpp.debug',
output: { parsed: true, errors, backtrace, log_path },
metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id }
}
}
// If target is provided, attempt a simple debug run with gdb batch mode
if (target) {
const gdbCmd = `gdb -batch -ex "run" -ex "bt" --args ${target}`
try {
const out = execFileSync('gdb', ['-batch', '-ex', 'run', '-ex', 'bt', '--', target], {
cwd: project_root,
stdio: 'pipe',
encoding: 'utf-8',
timeout: 60000
})
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, 0))
return {
status: 'ok',
call_id: call.call_id,
tool_name: 'cpp.debug',
output: { target, debug_output: out.slice(0, 5000) },
metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id }
}
} catch (e: any) {
// GDB might fail if the program runs successfully or has issues
await this.emit(event_sink, ctx, this.command_completed(cmd_id, ctx, 0, 0))
return {
status: 'ok',
call_id: call.call_id,
tool_name: 'cpp.debug',
output: { target, debug_output: e.message || String(e), exit_code: e.status || 1 },
metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id }
}
}
}
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.debug', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: 'Either target or log_path required', retryability: 'not_retryable', semantic_signature: 'cpp.debug' }, metadata: { timestamp: new Date().toISOString() } }
} catch (e: any) {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.debug', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message || String(e), retryability: 'not_retryable', semantic_signature: 'cpp.debug' }, metadata: { timestamp: new Date().toISOString() } }
}
})
// FR-017: cpp.fix — Apply scoped fix to C++ file based on diagnostic
registry.register('cpp.fix', {
name: 'cpp.fix',
category: 'toolchain',
description: 'Apply a scoped fix to a C++ file (FR-017: scoped fix execution)',
input_schema: {
type: 'object',
properties: {
file: { type: 'string', description: 'Source file to fix' },
line: { type: 'number', description: 'Line number to fix' },
fix_type: { type: 'string', enum: ['include', 'syntax', 'memory', 'nullptr', 'typo'], description: 'Type of fix to apply' },
fix_content: { type: 'string', description: 'Content to insert/replace' }
},
required: ['file', 'line', 'fix_type']
},
permissions: { read: true, write: true, network: false },
streaming: false
}, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
const { file, line, fix_type, fix_content } = call.arguments as any
try {
const fullPath = join(project_root, file)
if (!existsSync(fullPath)) {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.fix', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: `File not found: ${file}`, retryability: 'not_retryable', semantic_signature: 'cpp.fix' }, metadata: { timestamp: new Date().toISOString() } }
}
const lines = readFileSync(fullPath, 'utf-8').split('\n')
const targetLine = line - 1 // Convert to 0-indexed
if (targetLine < 0 || targetLine >= lines.length) {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.fix', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: `Line ${line} out of range (file has ${lines.length} lines)`, retryability: 'not_retryable', semantic_signature: 'cpp.fix' }, metadata: { timestamp: new Date().toISOString() } }
}
// FR-017: Apply scoped fix based on fix_type
let newLines = [...lines]
switch (fix_type) {
case 'include':
// Insert include at top of file (after any existing includes)
const insertIdx = newLines.findIndex(l => l.startsWith('#include') || l.startsWith('#define') || l.startsWith('#ifdef'))
if (insertIdx >= 0) {
newLines.splice(insertIdx, 0, fix_content || '#include <new_header>')
} else {
newLines.unshift(fix_content || '#include <new_header>')
}
break
case 'syntax':
case 'memory':
case 'nullptr':
// Replace the target line
newLines[targetLine] = fix_content || newLines[targetLine]
break
case 'typo':
// Simple line replacement
newLines[targetLine] = fix_content || newLines[targetLine]
break
default:
newLines[targetLine] = fix_content || newLines[targetLine]
}
writeFileSync(fullPath, newLines.join('\n'), 'utf-8')
await this.emit_evidence(event_sink, ctx, cmd_id, 'code_fix', { file, line, fix_type })
return {
status: 'ok',
call_id: call.call_id,
tool_name: 'cpp.fix',
output: { file, line, fix_type, applied: true, lines_affected: 1 },
metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id }
}
} catch (e: any) {
return { status: 'error', call_id: call.call_id, tool_name: 'cpp.fix', error: { error_id: call.call_id, kind: 'tool_error', severity: 'error', message: e.message || String(e), retryability: 'not_retryable', semantic_signature: 'cpp.fix' }, metadata: { timestamp: new Date().toISOString() } }
}
})
// FR-017: cpp.verify — Re-run build/test to verify fix and emit evidence
registry.register('cpp.verify', {
name: 'cpp.verify',
category: 'toolchain',
description: 'Verify fix by re-running build and tests (FR-017: evidence-backed verification)',
input_schema: {
type: 'object',
properties: {
verify_target: { type: 'string', enum: ['build', 'test', 'both'], default: 'both', description: 'What to verify' },
build_target: { type: 'string', description: 'CMake build target' }
},
required: []
},
permissions: { read: true, write: false, network: false },
streaming: false
}, async (call, tool_ctx) => {
const ctx: CppContext = tool_ctx || {}
const cmd_id = this.cmd_id()
const { verify_target = 'both', build_target } = call.arguments as any
const results: Record<string, any> = {}
// Run build verification
if (verify_target === 'build' || verify_target === 'both') {
const buildResult = builder.build(project_root + '/build', build_target)
results.build = { ok: buildResult.ok, output: buildResult.output?.slice(0, 2000), diagnostics_count: buildResult.diagnostics?.length }
const diag_ids = await this.emit_diagnostics(event_sink, ctx, cmd_id + '_build', buildResult.diagnostics.map(d => ({ file: d.file || '', line: d.line || 0, column: 0, severity: d.severity || 'error', message: d.message, semantic_signature: `verify.build.${d.file}.${d.line}` })))
}
// Run test verification
if (verify_target === 'test' || verify_target === 'both') {
const testResult = tester.run_tests(project_root + '/build')
results.test = { ok: testResult.ok, passed: testResult.passed, failed: testResult.failed, total: testResult.total }
await this.emit_evidence(event_sink, ctx, cmd_id + '_test', 'verification', results.test)
}
const all_ok = (verify_target === 'both' ? (results.build?.ok && results.test?.ok) : (verify_target === 'build' ? results.build?.ok : results.test?.ok))
return {
status: all_ok ? 'ok' : 'error',
call_id: call.call_id,
tool_name: 'cpp.verify',
output: results,
metadata: { timestamp: new Date().toISOString(), command_run_id: cmd_id }
}
})
}
// ===== Private: event emission (no runtime import — uses duck-typed EventSink) =====

View File

@@ -65,6 +65,31 @@ export const CPP_TOOLCHAIN_CAPABILITY: CapabilityManifestV1 = {
permissions: { read_paths: { allow: ['*'] }, execute: true, network: false },
input_schema: { type: 'object', properties: { file: { type: 'string' }, line: { type: 'number' }, column: { type: 'number' } } },
output_schema: { type: 'object', properties: {} }
},
// FR-017: Added missing C++ tools
{
name: 'cpp.debug', version: 1,
description: 'Debug a C++ program or parse crash logs (gdb/lldb/valgrind)',
category: 'debug' as const,
permissions: { read_paths: { allow: ['*'] }, execute: true, network: false },
input_schema: { type: 'object', properties: { target: { type: 'string' }, source_file: { type: 'string' }, log_path: { type: 'string' }, format: { type: 'string', enum: ['gdb', 'lldb', 'valgrind', 'asan'] } } },
output_schema: { type: 'object', properties: {} }
},
{
name: 'cpp.fix', version: 1,
description: 'Apply a scoped fix to a C++ file (include/syntax/memory/nullptr/typo)',
category: 'build' as const,
permissions: { read_paths: { allow: ['*'] }, write_paths: { allow: ['*.cpp', '*.cc', '*.cxx', '*.h', '*.hpp'] }, execute: false, network: false },
input_schema: { type: 'object', properties: { file: { type: 'string' }, line: { type: 'number' }, fix_type: { type: 'string', enum: ['include', 'syntax', 'memory', 'nullptr', 'typo'] }, fix_content: { type: 'string' } } },
output_schema: { type: 'object', properties: {} }
},
{
name: 'cpp.verify', version: 1,
description: 'Verify fix by re-running build and tests with evidence emission',
category: 'test' as const,
permissions: { read_paths: { allow: ['*'] }, execute: true, network: false },
input_schema: { type: 'object', properties: { verify_target: { type: 'string', enum: ['build', 'test', 'both'] }, build_target: { type: 'string' } } },
output_schema: { type: 'object', properties: {} }
}
]
}

View File

@@ -1,46 +0,0 @@
/**
* A1 regression: command injection fix — execFileSync, not execSync
* Verifies CMakeConfigurator, CppBuilder, CppcheckRunner use execFileSync.
*/
import { describe, it, expect } from 'bun:test'
import { readFileSync } from 'fs'
import { join } from 'path'
const SRC_ROOT = join(import.meta.dir, '..', 'src')
function reads_file(relative: string): string {
return readFileSync(join(SRC_ROOT, relative), 'utf-8')
}
describe('A1: Command injection fix', () => {
it('CMakeConfigurator uses execFileSync, not execSync', () => {
const src = reads_file('build/CMakeConfigurator.ts')
expect(src).toContain('execFileSync')
expect(src).not.toMatch(/\bexecSync\s*\(/)
})
it('CppBuilder uses execFileSync, not execSync', () => {
const src = reads_file('build/CppBuilder.ts')
expect(src).toContain('execFileSync')
expect(src).not.toMatch(/\bexecSync\s*\(/)
})
it('CppcheckRunner uses execFileSync, not execSync', () => {
const src = reads_file('analysis/CppcheckRunner.ts')
expect(src).toContain('execFileSync')
expect(src).not.toMatch(/\bexecSync\s*\(/)
})
it('CMakeConfigurator passes args as array to execFileSync', () => {
const src = reads_file('build/CMakeConfigurator.ts')
expect(src).toMatch(/execFileSync\s*\(\s*'cmake'/)
expect(src).not.toMatch(/execFileSync\s*\(\s*'cmake'\s*,\s*[`'"]/)
})
it('CppBuilder passes args as array to execFileSync', () => {
const src = reads_file('build/CppBuilder.ts')
expect(src).toMatch(/execFileSync\s*\(\s*'cmake'/)
expect(src).not.toMatch(/execFileSync\s*\(\s*'cmake'\s*,\s*[`'"]/)
})
})

View File

@@ -1,11 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
"skipLibCheck": true,
"outDir": "./dist"
},
"include": ["src"],
"references": [
{ "path": "../contracts" }
]
"include": ["src/**/*"]
}