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>
114 lines
4.5 KiB
TypeScript
Executable File
114 lines
4.5 KiB
TypeScript
Executable File
/**
|
|
* ExperienceMinerRole - Pattern extraction worker
|
|
* Analyzes completed tasks for reusable patterns.
|
|
* DD §8.4.
|
|
*
|
|
* @module packages/workers/src/roles/ExperienceMinerRole
|
|
*/
|
|
|
|
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<{
|
|
category: string
|
|
pattern: string
|
|
source_task_id: string
|
|
description: string
|
|
}>
|
|
summary: string
|
|
}
|
|
|
|
export class ExperienceMinerRole {
|
|
private runtime: WorkerRuntime
|
|
|
|
constructor(runtime: WorkerRuntime) {
|
|
this.runtime = runtime
|
|
}
|
|
|
|
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: [],
|
|
summary: ''
|
|
}
|
|
|
|
try {
|
|
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']
|
|
|
|
if (task_ids.length === 0 && evidence_refs.length === 0) {
|
|
result.summary = 'No task or evidence refs available for memory mining'
|
|
return result
|
|
}
|
|
|
|
const messages = [
|
|
{ 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.2 })
|
|
for (const line of (analysis.content || '').split('\n')) {
|
|
const colon_idx = line.indexOf(':')
|
|
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 {
|
|
result.entries.push({
|
|
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.summary = `No supported memory candidates extracted from ${task_ids.length} tasks`
|
|
} else {
|
|
result.status = 'completed'
|
|
result.summary = `Created ${result.entries.length} memory candidates from ${task_ids.length} tasks`
|
|
}
|
|
|
|
this.runtime.checkpoint('experience_mining_completed', { candidates: result.entries.length })
|
|
return result
|
|
|
|
} catch (error) {
|
|
result.status = 'blocked'
|
|
result.summary = error instanceof Error ? error.message : String(error)
|
|
return result
|
|
}
|
|
}
|
|
} |