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:
@@ -8,6 +8,8 @@
|
||||
|
||||
import { WorkerRuntime } from '../WorkerRuntime.js'
|
||||
|
||||
const MEMORY_TYPES = new Set(['project_rule', 'toolchain_rule', 'skill_update', 'debug_experience'])
|
||||
|
||||
export interface ExperienceMinerResult {
|
||||
status: 'completed' | 'no_patterns' | 'blocked'
|
||||
entries: Array<{
|
||||
@@ -26,7 +28,7 @@ export class ExperienceMinerRole {
|
||||
this.runtime = runtime
|
||||
}
|
||||
|
||||
async run(mine_spec: { task_ids: string[]; focus_categories?: string[] }): Promise<ExperienceMinerResult> {
|
||||
async run(mine_spec: { task_ids?: string[]; focus_categories?: string[]; source_refs?: Array<Record<string, unknown>>; evidence_refs?: string[] }): Promise<ExperienceMinerResult> {
|
||||
const result: ExperienceMinerResult = {
|
||||
status: 'no_patterns',
|
||||
entries: [],
|
||||
@@ -34,68 +36,73 @@ export class ExperienceMinerRole {
|
||||
}
|
||||
|
||||
try {
|
||||
this.runtime.emit('mining.started', { task_ids: mine_spec.task_ids })
|
||||
const task_ids = mine_spec.task_ids ?? []
|
||||
const evidence_refs = mine_spec.evidence_refs ?? task_ids.map((task_id) => `task:${task_id}`)
|
||||
const focus = mine_spec.focus_categories?.length ? mine_spec.focus_categories : ['project_rule', 'toolchain_rule', 'debug_experience']
|
||||
|
||||
// Read completed task results to extract patterns
|
||||
const task_summaries: string[] = []
|
||||
for (const task_id of mine_spec.task_ids) {
|
||||
try {
|
||||
// Emit that we're reading a task
|
||||
this.runtime.emit('mining.task', { task_id })
|
||||
task_summaries.push(`Task ${task_id}: completed`)
|
||||
} catch { /* skip failed task reads */ }
|
||||
}
|
||||
|
||||
if (task_summaries.length === 0) {
|
||||
result.status = 'no_patterns'
|
||||
result.summary = 'No completed tasks available for mining'
|
||||
if (task_ids.length === 0 && evidence_refs.length === 0) {
|
||||
result.summary = 'No task or evidence refs available for memory mining'
|
||||
return result
|
||||
}
|
||||
|
||||
// Use LLM to extract patterns
|
||||
const messages = [
|
||||
{ role: 'system', content: 'You are an experience mining expert. Extract reusable patterns, best practices, and lessons learned from completed tasks. Output one pattern per line in format: CATEGORY: pattern description' },
|
||||
{ role: 'user', content: `Analyze these completed tasks and extract reusable patterns:\n${task_summaries.join('\n')}\n\nFocus categories: ${(mine_spec.focus_categories || ['implementation', 'debugging', 'testing']).join(', ')}` }
|
||||
{ role: 'system', content: 'Extract durable learning candidates only when supported by evidence. Output one candidate per line as memory_type: concise summary. Valid memory_type values: project_rule, toolchain_rule, skill_update, debug_experience. Do not promote or archive memories.' },
|
||||
{ role: 'user', content: `Evidence refs:\n${evidence_refs.join('\n')}\n\nTask ids: ${task_ids.join(', ') || '(none)'}\nFocus categories: ${focus.join(', ')}` }
|
||||
]
|
||||
|
||||
try {
|
||||
const analysis = await this.runtime.call_llm({ messages, max_tokens: 2048, temperature: 0.3 })
|
||||
const lines = (analysis.content || '').split('\n').filter(l => l.includes(':'))
|
||||
|
||||
for (const line of lines) {
|
||||
const analysis = await this.runtime.call_llm({ messages, max_tokens: 2048, temperature: 0.2 })
|
||||
for (const line of (analysis.content || '').split('\n')) {
|
||||
const colon_idx = line.indexOf(':')
|
||||
if (colon_idx > 0) {
|
||||
const category = line.slice(0, colon_idx).trim().toLowerCase()
|
||||
const pattern = line.slice(colon_idx + 1).trim()
|
||||
if (pattern.length > 5) {
|
||||
result.entries.push({
|
||||
category,
|
||||
pattern,
|
||||
source_task_id: mine_spec.task_ids[0] || '',
|
||||
description: pattern
|
||||
})
|
||||
}
|
||||
}
|
||||
if (colon_idx <= 0) continue
|
||||
const category = line.slice(0, colon_idx).trim().toLowerCase()
|
||||
const memory_type = MEMORY_TYPES.has(category) ? category : 'project_rule'
|
||||
const pattern = line.slice(colon_idx + 1).trim()
|
||||
if (pattern.length < 8) continue
|
||||
result.entries.push({ category: memory_type, pattern, source_task_id: task_ids[0] || '', description: pattern })
|
||||
}
|
||||
} catch {
|
||||
// LLM unavailable — extract basic patterns from task metadata
|
||||
result.entries.push({
|
||||
category: 'execution',
|
||||
pattern: 'Tasks completed via Executor→LLM→Tool loop',
|
||||
source_task_id: mine_spec.task_ids[0] || '',
|
||||
description: 'Standard execution pattern for code changes'
|
||||
category: 'project_rule',
|
||||
pattern: `Review evidence before promoting memory from ${evidence_refs[0] || task_ids[0]}`,
|
||||
source_task_id: task_ids[0] || '',
|
||||
description: 'LLM unavailable; created a conservative candidate that requires human/runtime review before promotion.',
|
||||
})
|
||||
}
|
||||
|
||||
if (result.entries.length === 0 && evidence_refs.length > 0) {
|
||||
const category = focus.find((item) => MEMORY_TYPES.has(item)) || 'project_rule'
|
||||
result.entries.push({
|
||||
category,
|
||||
pattern: `Review evidence before promoting memory from ${evidence_refs[0]}`,
|
||||
source_task_id: task_ids[0] || '',
|
||||
description: 'Created a conservative candidate because no structured LLM-supported pattern was returned.',
|
||||
})
|
||||
}
|
||||
|
||||
for (const entry of result.entries) {
|
||||
const candidate_id = `mem_${crypto.randomUUID()}`
|
||||
this.runtime.emit('memory.candidate.created', {
|
||||
event_id: `evt_${candidate_id}`,
|
||||
candidate_id,
|
||||
source_ref: {
|
||||
entity_type: entry.source_task_id ? 'task' : 'evidence',
|
||||
entity_id: entry.source_task_id || evidence_refs[0] || '',
|
||||
},
|
||||
memory_type: entry.category,
|
||||
summary: entry.pattern,
|
||||
evidence_refs,
|
||||
})
|
||||
}
|
||||
|
||||
if (result.entries.length === 0) {
|
||||
result.status = 'no_patterns'
|
||||
result.summary = `No patterns extracted from ${mine_spec.task_ids.length} tasks`
|
||||
result.summary = `No supported memory candidates extracted from ${task_ids.length} tasks`
|
||||
} else {
|
||||
result.status = 'completed'
|
||||
result.summary = `Mined ${result.entries.length} patterns from ${mine_spec.task_ids.length} tasks`
|
||||
result.summary = `Created ${result.entries.length} memory candidates from ${task_ids.length} tasks`
|
||||
}
|
||||
|
||||
this.runtime.checkpoint('mining_completed', { patterns_found: result.entries.length })
|
||||
this.runtime.checkpoint('experience_mining_completed', { candidates: result.entries.length })
|
||||
return result
|
||||
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user