feat(self): 자기 평가 질의에 기능 인벤토리 강제 주입 + 충돌 신뢰도 비교 권고 (v2.2.226)
[자기 지식 구식화 — 마지막 구멍 봉쇄] 인벤토리를 자동 생성해도(v2.2.225) 모델이 검색 없이 기억으로 답하면 무용 — 실사례: 답변 말미 "출처: 모델 지식 (검색 미사용)" 후 이미 있는 기능 (CoVe·멀티스텝 플래닝·노후점검 자동화)을 신규 제안. 프롬프트 규칙은 검색을 강제할 수 없으므로 scheduleContext 와 동일 패턴으로 해결: - selfAssessContext: "기능 개선/고도화/self-evolving/무슨 기능" 류 질의 감지 시 인벤토리 전문을 RAG 경쟁 없이 결정론적 주입 + "이미 있는 기능 신규 제안 금지, '현재 X 있음 — 빠진 증분 Y' 형태" 지시. 인벤토리 미생성 시 정직 안내. [충돌 해결사 — 권고까지만, 자동 결정은 안 함] - conflictScan 에 신뢰도 비교 추가: 양쪽 frontmatter(source_trust_level S~D, confidence_score) + 최신성으로 "신규/기존 우선 권고" 생성. 메타데이터 없거나 비등하면 권고 보류 (근거 없는 권고 금지). 삭제·덮어쓰기는 여전히 사람 결정. 테스트 17건 추가 (질의 감지·인벤토리 주입·신뢰 파싱·권고 분기). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,48 @@ export interface ConflictFinding {
|
||||
newDoc: string;
|
||||
existingDoc: string;
|
||||
summary: string;
|
||||
/** 신뢰도 비교 기반 우선 권고 (자동 삭제는 하지 않음 — 결정은 사람). */
|
||||
recommend: string;
|
||||
}
|
||||
|
||||
// ── 신뢰도 비교 — frontmatter(source_trust_level·confidence_score) + 최신성 ──
|
||||
|
||||
export interface DocTrust {
|
||||
trustLevel: string; // S/A/B/C/D 또는 ''
|
||||
confidence: number; // 0~1, 미상 -1
|
||||
mtimeMs: number;
|
||||
}
|
||||
|
||||
const TRUST_RANK: Record<string, number> = { S: 4, A: 3, B: 2, C: 1, D: 0 };
|
||||
|
||||
/** 문서 머리 frontmatter 에서 신뢰 메타데이터 추출 (없으면 빈 값). */
|
||||
export function parseDocTrust(content: string, mtimeMs: number): DocTrust {
|
||||
const head = (content || '').slice(0, 1200);
|
||||
const trust = head.match(/^source_trust_level:\s*["']?([SABCD])["']?\s*$/mi)?.[1]?.toUpperCase() || '';
|
||||
const confRaw = head.match(/^confidence_score:\s*["']?([\d.]+)["']?\s*$/mi)?.[1];
|
||||
const confidence = confRaw !== undefined && Number.isFinite(Number(confRaw)) ? Math.max(0, Math.min(1, Number(confRaw))) : -1;
|
||||
return { trustLevel: trust, confidence, mtimeMs };
|
||||
}
|
||||
|
||||
const fmtTrust = (t: DocTrust) =>
|
||||
`${t.trustLevel || '등급없음'}·${t.confidence >= 0 ? t.confidence.toFixed(1) : '점수없음'}`;
|
||||
|
||||
/**
|
||||
* 충돌 양쪽의 신뢰도를 비교해 우선 권고 문자열 생성.
|
||||
* 점수 = 신뢰등급(0~4)×2 + confidence(0~1)×2 + 최신성(+1). 메타데이터가 양쪽 다
|
||||
* 없으면 권고 보류 — 근거 없는 권고는 하지 않는다.
|
||||
*/
|
||||
export function buildTrustRecommendation(newer: DocTrust, older: DocTrust): string {
|
||||
const hasMetaN = !!newer.trustLevel || newer.confidence >= 0;
|
||||
const hasMetaO = !!older.trustLevel || older.confidence >= 0;
|
||||
if (!hasMetaN && !hasMetaO) return '권고 보류 — 양쪽 모두 신뢰 메타데이터 없음 (내용으로 직접 판단 필요)';
|
||||
const score = (t: DocTrust, isNewer: boolean) =>
|
||||
(TRUST_RANK[t.trustLevel] ?? 0) * 2 + (t.confidence >= 0 ? t.confidence : 0) * 2 + (isNewer ? 1 : 0);
|
||||
const sNew = score(newer, true), sOld = score(older, false);
|
||||
if (Math.abs(sNew - sOld) < 1) return `권고 보류 — 신뢰도 비등 (신규 ${fmtTrust(newer)} vs 기존 ${fmtTrust(older)})`;
|
||||
return sNew > sOld
|
||||
? `권고: **신규 우선** (신규 ${fmtTrust(newer)}·최신 vs 기존 ${fmtTrust(older)})`
|
||||
: `권고: **기존 우선** (기존 ${fmtTrust(older)} vs 신규 ${fmtTrust(newer)}·최신)`;
|
||||
}
|
||||
|
||||
/** 1회 스캔 실행. 반환: 요약 문자열. */
|
||||
@@ -114,7 +156,14 @@ export async function runConflictScanOnce(): Promise<string> {
|
||||
if (!m) continue;
|
||||
const parsed = JSON.parse(m[0]);
|
||||
if (parsed?.conflict === true && String(parsed?.summary || '').trim()) {
|
||||
findings.push({ newDoc: rel, existingDoc: n.relativePath, summary: String(parsed.summary).slice(0, 200) });
|
||||
let existingMtime = 0;
|
||||
try { existingMtime = fs.statSync(n.filePath).mtimeMs; } catch { /* 0 유지 */ }
|
||||
findings.push({
|
||||
newDoc: rel,
|
||||
existingDoc: n.relativePath,
|
||||
summary: String(parsed.summary).slice(0, 200),
|
||||
recommend: buildTrustRecommendation(parseDocTrust(content, t.m), parseDocTrust(existing, existingMtime)),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -128,7 +177,7 @@ export async function runConflictScanOnce(): Promise<string> {
|
||||
const reportFile = path.join(brainPath, REPORT_REL);
|
||||
const block = [
|
||||
`\n## ${new Date().toLocaleString()} — 충돌 ${findings.length}건`,
|
||||
...findings.map(f => `- ⚔️ 신규 **${f.newDoc}** ↔ 기존 **${f.existingDoc}**: ${f.summary}\n → 어느 쪽을 유지할지 결정 후 다른 쪽을 수정/삭제하세요 (시스템은 덮어쓰지 않음).`),
|
||||
...findings.map(f => `- ⚔️ 신규 **${f.newDoc}** ↔ 기존 **${f.existingDoc}**: ${f.summary}\n → ${f.recommend}\n → 어느 쪽을 유지할지 결정 후 다른 쪽을 수정/삭제하세요 (시스템은 덮어쓰지 않음).`),
|
||||
].join('\n');
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(reportFile), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user