feat(growth): 자기 지식 자동화 + 회귀 경보 + 충돌 스캔 + Critic 게이트 확장 (v2.2.225)
[근본 수정 — 자가검증 구식 정보 버그] ASTRA 자기 지식이 사람이 쓴 스냅샷(selfIdentity 블록·아키텍처 위키 문서)에 의존해 릴리스마다 구식이 됐고, 자기 개선 제안에서 이미 있는 기능을 신규 제안하는 오류가 반복됨. 수정: - featureInventory.ts: 활성화 시 package.json(contributes.commands/configuration) + POST_ANSWER_HOOKS 레지스트리에서 "ASTRA 기능 인벤토리" 문서를 두뇌에 기계 생성 (버전 변경 시 자동 재생성 — 사람이 갱신을 잊을 수 없는 구조). - selfIdentity: "자기 기능 평가·제안 전 인벤토리와 대조, 기억 의존 서술 금지" 규칙. [검증-피드백-재설계 파이프라인 보강 — 의견 검토 후 역제안 3건] - A-1 골든셋 회귀 경보: 주간 사이클이 metrics-history.jsonl 적립 + 직전 대비 recall@1 -10%p 또는 MRR -0.08 하락 시 ⚠️ + 그 기간 추가된 문서를 용의자로 제시(regression-alert.md). 자동 롤백 없음 — 판단은 사람. - A-2 신규 지식 충돌 스캔(conflictScan.ts): 일일(사전 소화와 같은 슬롯) 신규/변경 문서를 기존 유사 top-2와 LLM 모순 비교 → 충돌 시 conflict-report.md + "기존 A vs 신규 B" 알림. 쓰기 주체(Datacollect/수동/Research) 무관 포착. 런당 비교 ≤5건·최초 실행 24h 한정 (폭주 방지). - A-3 criticLoop 게이트 확장: 업무 turn 외에도 "근거 약함(top<0.25) + 단정 표현(수치·날짜·확언)" 트리거 추가. 전 답변 강제 2-pass 는 기각 — intrinsic self-correction 은 외부 신호 없이 효과 없음(arXiv 2310.01798). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* ASTRA 기능 인벤토리 자동 생성 — 자기 지식 구식화의 근본 수정.
|
||||
*
|
||||
* 문제 (반복 발생한 심각 버그): ASTRA 의 자기 지식이 *사람이 쓴 스냅샷*
|
||||
* (selfIdentity 블록, "ASTRA 자기 아키텍처" 위키 문서)에 의존했다. 스냅샷은
|
||||
* 작성 시점에 박제되므로 릴리스마다 구식이 되고, ASTRA 는 자기 평가·개선 제안에서
|
||||
* 이미 있는 기능을 "신규 제안"하거나(자가검증 구식 정보 사용) 없는 기능을 주장한다.
|
||||
*
|
||||
* 수정: 소스 오브 트루스(package.json 의 contributes.commands / configuration +
|
||||
* POST_ANSWER_HOOKS 레지스트리)에서 인벤토리 문서를 **활성화 시점에 기계 생성**해
|
||||
* 두뇌에 쓴다. 버전이 바뀌면 자동 재생성 — 사람이 갱신을 잊을 수 없는 구조.
|
||||
* RAG 가 이 문서를 검색하므로 자기 기능 질문·자기 개선 제안의 근거가 항상 현행이다.
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { getActiveBrainProfile, logError, logInfo } from '../utils';
|
||||
import { POST_ANSWER_HOOKS } from '../agent/postAnswerHooks';
|
||||
|
||||
export const INVENTORY_FILE = 'ASTRA 기능 인벤토리.md';
|
||||
const STATE_KEY = 'astra.inventoryVersion';
|
||||
|
||||
/** 답변 후 훅 id → 한 줄 설명. 레지스트리에 새 훅이 추가되면 id 는 자동 노출되고
|
||||
* 설명만 여기 한 줄 추가 (설명 누락 시에도 id 는 문서에 나타난다 — 침묵 누락 방지). */
|
||||
const HOOK_DESCRIPTIONS: Record<string, string> = {
|
||||
'devil-rebuttal': 'Devil Agent 반박 카드 (활성화 시)',
|
||||
'self-check': '답변 검증 LLM 호출 — 검색 근거 대조 (opt-in)',
|
||||
'term-validator': '글로서리 금지 용어 결정론적 검사',
|
||||
'requirement-coverage': '업무 필수 요소 커버리지 결정론적 검사',
|
||||
'confidence-escalation': '확신도 산출 + 인간 검토 에스컬레이션 + Reflection 기록',
|
||||
'critic-loop': '문제 신호(요소 누락/저확신/근거 약함+단정) 턴만 Critic LLM 검수 1회',
|
||||
};
|
||||
|
||||
function stripMd(s: string): string {
|
||||
return (s || '').replace(/\*\*|`|\[|\]/g, '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
/** package.json contributes 에서 인벤토리 마크다운 생성 (순수 — 테스트 가능). */
|
||||
export function buildInventoryMarkdown(pkg: any, nowIso: string): string {
|
||||
const version = String(pkg?.version || '?');
|
||||
const commands: Array<{ command: string; title: string }> = pkg?.contributes?.commands || [];
|
||||
const configProps: Record<string, any> = pkg?.contributes?.configuration?.properties || {};
|
||||
|
||||
const lines: string[] = [
|
||||
'---',
|
||||
'type: reference',
|
||||
'title: "ASTRA 기능 인벤토리 (자동 생성)"',
|
||||
`version: "${version}"`,
|
||||
`generated_at: ${nowIso}`,
|
||||
'aliases: ["ASTRA 기능 목록", "ASTRA 명령어", "내 기능", "ASTRA가 할 수 있는 것", "기능 인벤토리", "ASTRA capabilities"]',
|
||||
'---',
|
||||
'',
|
||||
`# ASTRA 기능 인벤토리 — v${version} (자동 생성)`,
|
||||
'',
|
||||
'> ⚙️ 이 문서는 Astra 활성화 시 **소스 코드(package.json)에서 기계 생성**됩니다 — 수동 편집 금지 (버전 변경 시 덮어씀).',
|
||||
'> 자기 기능에 대한 질문·자기 개선 제안은 이 문서가 **항상 현행** 근거입니다. 서사적 설명은 [[ASTRA 자기 아키텍처]] 참고.',
|
||||
'',
|
||||
`## 사용자 명령 (${commands.length}개)`,
|
||||
...commands.map(c => `- ${stripMd(c.title)}`),
|
||||
'',
|
||||
`## 설정으로 제어되는 동작·자동화 (${Object.keys(configProps).length}개)`,
|
||||
...Object.entries(configProps).map(([key, prop]: [string, any]) => {
|
||||
const desc = stripMd(String(prop?.markdownDescription || prop?.description || ''));
|
||||
const first = desc.split(/(?<=[.다음])\s/)[0] || desc;
|
||||
return `- \`${key.replace('g1nation.', '')}\` — ${first.slice(0, 160)}`;
|
||||
}),
|
||||
'',
|
||||
`## 답변 후 자동 검증 훅 (${POST_ANSWER_HOOKS.length}단계 — 매 답변 후 실행)`,
|
||||
...POST_ANSWER_HOOKS.map(h => `- \`${h.id}\` — ${HOOK_DESCRIPTIONS[h.id] || '(설명 미등록 — 코드 참조)'}`),
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 버전이 바뀌었거나 문서가 없으면 두뇌에 인벤토리를 재생성. 활성화 시 1회 호출
|
||||
* (fire-and-forget — 실패해도 turn 에 영향 없음).
|
||||
*/
|
||||
export async function ensureFeatureInventory(context: vscode.ExtensionContext): Promise<void> {
|
||||
try {
|
||||
const brain = getActiveBrainProfile();
|
||||
if (!brain?.localBrainPath || !fs.existsSync(brain.localBrainPath)) return;
|
||||
const pkg = vscode.extensions.getExtension('g1nation.astra')?.packageJSON;
|
||||
if (!pkg) return;
|
||||
const version = String(pkg.version || '?');
|
||||
const file = path.join(brain.localBrainPath, INVENTORY_FILE);
|
||||
if (context.globalState.get<string>(STATE_KEY) === version && fs.existsSync(file)) return;
|
||||
|
||||
fs.writeFileSync(file, buildInventoryMarkdown(pkg, new Date().toISOString()), 'utf8');
|
||||
await context.globalState.update(STATE_KEY, version);
|
||||
logInfo('기능 인벤토리 재생성.', { version, file });
|
||||
} catch (e: any) {
|
||||
logError('기능 인벤토리 생성 실패 (무시).', { error: e?.message ?? String(e) });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user