feat: Self-Evolving Digital Employee OS P0~P6 + 캘린더 충돌 게이트

신뢰성 코어 (P1~P2):
- Requirement Graph: 업무 유형(회의록/시장조사/업무조사/일정) 필수 요소 주입 + 커버리지 hook
- Confidence Engine(0~100 결정론적) / Escalation Engine(검토 요청) / Epistemic Guard(모름·추정·확실 3분류)
- Provenance: citationTrace 에 출처 수정일·오래됨 경고
- Critic Loop: 문제 신호 turn 만 LLM 검수 1회 + 보완 카드

성장 루프 (P3):
- Gap Detector(Requirement-Knowledge) / Need Engine(30/25/20/15/10 공식) / Knowledge Inventory
- Learning Queue(proposed 전용 병합 — 승인은 사람만) / Decision Journal / Reflection 기록
- 반복 누락 요소(3회+)는 다음 turn 체크리스트에 자동 강조 (T5 루프)

지식 운영 (P4) + 기억 (P5) + 학습 실행 (P6):
- Knowledge Validation + Belief Revision(중복 reject·충돌 시 update/add 권고)
- Knowledge Decay(분야별 반감기 감사) / Knowledge Debt(blocked x impact)
- Organizational Memory(.astra/organization.md 상시 주입)
- Research Agent(approved 큐 -> 조사 브리프+추정 라벨 초안+Validation 게이트 -> proposals/)
- Skill Score(전/후반 추세) + Success Pattern DB(전요소충족+확신도90+ 자동 적재)

병렬 트랙:
- 캘린더 충돌 게이트: conflictCheck + 구조화 이벤트 캐시 + create_calendar_event 차단(force 는 사용자 승인 후)
- Task Eval Harness: 회의록 골든셋 자동 채점 명령 + 성장 리포트/학습 큐/노후 점검 명령

신규 모듈 17종(src/intelligence/), VS Code 명령 5종, 설정 11종, 테스트 +89건(전체 508 통과).
설계 문서: docs/SELF_EVOLVING_OS_MASTER_PLAN.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 13:42:09 +09:00
parent cbc2558550
commit 2afd1ac589
41 changed files with 4364 additions and 2 deletions
+174
View File
@@ -0,0 +1,174 @@
/**
* Knowledge Validation / Belief Revision / Decay / Debt
* (Self-Evolving OS Phase 4 — 지식 운영) 테스트.
*/
import {
validateKnowledgeCandidate,
jaccardSimilarity,
ExistingKnowledgeRef,
} from '../src/intelligence/knowledgeValidation';
import {
auditKnowledgeDecay,
classifyDecayRule,
decayFactor,
formatDecayReport,
} from '../src/intelligence/knowledgeDecay';
import { computeKnowledgeDebt, formatNeedsMarkdown } from '../src/intelligence/needEngine';
import type { ReflectionRecord } from '../src/intelligence/reflectionStore';
const NOW = Date.parse('2026-06-11T00:00:00Z');
const DAY = 86400000;
describe('knowledgeValidation', () => {
const existing: ExistingKnowledgeRef[] = [
{
title: 'GA4 전환율 가이드',
content: 'GA4 전환율 계산은 전환수 나누기 세션수 기준이며 보고서는 탐색 분석에서 본다',
lastUpdated: NOW - 100 * DAY,
},
];
it('출처 있고 중복/충돌 없는 신선한 후보 → accept', () => {
const r = validateKnowledgeCandidate(
{ title: '쿠팡 SEO', content: '쿠팡 검색 알고리즘은 판매량과 리뷰 점수를 핵심 신호로 사용한다', source: 'https://example.com', collectedAt: '2026-06-10T00:00:00Z' },
existing, { nowMs: NOW },
);
expect(r.verdict).toBe('accept');
expect(r.beliefRevision).toBe('add');
});
it('출처 없으면 자동 수용 불가 (review)', () => {
const r = validateKnowledgeCandidate(
{ title: 't', content: '완전히 새로운 내용의 지식 후보입니다 검증 테스트' },
existing, { nowMs: NOW },
);
expect(r.verdict).toBe('review');
expect(r.checks.hasSource).toBe(false);
});
it('거의 동일한 내용 → 중복 reject', () => {
const r = validateKnowledgeCandidate(
{ title: 'GA4', content: 'GA4 전환율 계산은 전환수 나누기 세션수 기준이며 보고서는 탐색 분석에서 본다', source: 's', collectedAt: '2026-06-10T00:00:00Z' },
existing, { nowMs: NOW },
);
expect(r.verdict).toBe('reject');
expect(r.checks.duplicateOf).toBe('GA4 전환율 가이드');
});
it('관련/충돌 + 후보가 더 최신 → review + update 권고 (Belief Revision)', () => {
const r = validateKnowledgeCandidate(
{ title: 'GA4 변경', content: 'GA4 전환율 계산은 이제 전환수 나누기 사용자수 기준으로 변경되었다 보고서 위치도 다르다', source: 's', collectedAt: '2026-06-01T00:00:00Z' },
existing, { nowMs: NOW },
);
expect(r.verdict).toBe('review');
expect(r.checks.conflictsWith).toBe('GA4 전환율 가이드');
expect(r.beliefRevision).toBe('update');
});
it('수집일이 1년 이상 경과 → stale review', () => {
const r = validateKnowledgeCandidate(
{ title: 't', content: '전혀 다른 주제의 오래된 지식 항목', source: 's', collectedAt: '2024-01-01T00:00:00Z' },
existing, { nowMs: NOW },
);
expect(r.verdict).toBe('review');
expect(r.checks.freshness).toBe('stale');
});
it('jaccardSimilarity — 동일 1.0, 무관 ~0', () => {
expect(jaccardSimilarity('같은 문장 테스트', '같은 문장 테스트')).toBe(1);
expect(jaccardSimilarity('완전히 다른 내용', '전혀 무관한 주제')).toBe(0);
});
});
describe('knowledgeDecay', () => {
it('분야 분류 — AI 30일, SEO 90일, 기본 365일', () => {
expect(classifyDecayRule('Topics/RAG_청킹_전략.md').halfLifeDays).toBe(30);
expect(classifyDecayRule('Topics/네이버_SEO_가이드.md').halfLifeDays).toBe(90);
expect(classifyDecayRule('Topics/요리_레시피.md').halfLifeDays).toBe(365);
});
it('decayFactor — 반감기 경과 시 0.5', () => {
expect(decayFactor(NOW - 30 * DAY, 30, NOW)).toBeCloseTo(0.5, 2);
expect(decayFactor(NOW, 30, NOW)).toBe(1);
});
it('audit — stale 우선 정렬 + 상태 판정', () => {
const items = auditKnowledgeDecay([
{ relPath: 'ai_guide.md', lastUpdated: NOW - 90 * DAY }, // AI 30일 반감 → 0.125 stale
{ relPath: '요리.md', lastUpdated: NOW - 30 * DAY }, // 일반 365일 → ~0.94 active
], { nowMs: NOW });
expect(items[0].relPath).toBe('ai_guide.md');
expect(items[0].status).toBe('stale');
expect(items[1].status).toBe('active');
});
it('formatDecayReport — 요약·권고 포함, 자동 삭제 없음 명시', () => {
const items = auditKnowledgeDecay([{ relPath: 'ai.md', lastUpdated: NOW - 200 * DAY }], { nowMs: NOW });
const md = formatDecayReport(items, { brainName: 'B', dateStr: 'now' });
expect(md).toContain('노후 1');
expect(md).toContain('자동 이동/삭제 없음');
});
});
describe('knowledgeDebt', () => {
function mk(partial: Partial<ReflectionRecord>): ReflectionRecord {
return {
ts: '2026-06-11T10:00:00.000Z', taskId: 'market-research', taskLabel: '시장조사',
confidenceScore: 50, confidenceBand: 'low', missing: [], escalated: false,
criticIssues: null, promptPreview: 'p', weakGrounding: true, gapSeverity: 'high',
...partial,
};
}
it('근거 없는 수행 turn 을 업무별로 집계, debtScore 정렬', () => {
const debt = computeKnowledgeDebt([
mk({}), mk({}), mk({ gapSeverity: 'medium' }),
mk({ taskId: 'meeting-minutes', taskLabel: '회의록', gapSeverity: 'low' }),
mk({ taskId: 'meeting-minutes', taskLabel: '회의록', weakGrounding: false }), // 부채 아님
]);
expect(debt[0].taskId).toBe('market-research');
expect(debt[0].blockedTurns).toBe(3);
expect(debt[0].impact).toBeGreaterThan(5);
expect(debt.find((d) => d.taskId === 'meeting-minutes')!.blockedTurns).toBe(1);
});
it('formatNeedsMarkdown 에 Debt 섹션 포함', () => {
const debt = computeKnowledgeDebt([mk({})]);
const md = formatNeedsMarkdown([], [], debt);
expect(md).toContain('Knowledge Debt');
expect(md).toContain('시장조사');
});
});
describe('orgMemoryBlock (P5)', () => {
const fsMod = require('fs');
const osMod = require('os');
const pathMod = require('path');
const { buildOrgMemoryBlock, ORG_MEMORY_REL_PATH } = require('../src/intelligence/orgMemoryBlock');
it('organization.md 가 있으면 블록 주입 + Human Override 명시', () => {
const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
const file = pathMod.join(brain, ORG_MEMORY_REL_PATH);
fsMod.mkdirSync(pathMod.dirname(file), { recursive: true });
fsMod.writeFileSync(file, '## 업무 방식\n- 속도 우선, 완벽주의 지양', 'utf8');
const block = buildOrgMemoryBlock(brain);
expect(block).toContain('[ORGANIZATIONAL MEMORY]');
expect(block).toContain('속도 우선');
expect(block).toContain('사용자 지시 우선');
});
it('파일 없으면 빈 문자열 (no-op)', () => {
const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
expect(buildOrgMemoryBlock(brain)).toBe('');
});
it('본문이 길면 cap + 잘림 안내', () => {
const brain = fsMod.mkdtempSync(pathMod.join(osMod.tmpdir(), 'astra-test-org-'));
const file = pathMod.join(brain, ORG_MEMORY_REL_PATH);
fsMod.mkdirSync(pathMod.dirname(file), { recursive: true });
fsMod.writeFileSync(file, 'x'.repeat(5000), 'utf8');
const block = buildOrgMemoryBlock(brain, { maxBodyLength: 1000 });
expect(block).toContain('잘림');
expect(block.length).toBeLessThan(2000);
});
});