Files
connectai/tests/researchSkill.test.ts
T
koriweb 2afd1ac589 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>
2026-06-11 13:42:09 +09:00

123 lines
5.7 KiB
TypeScript

/**
* Research Agent / Skill Score / Success Pattern DB (Self-Evolving OS Phase 6) 테스트.
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { parseBrief, fallbackBrief, runResearch, formatProposalMarkdown } from '../src/intelligence/researchAgent';
import {
computeSkillScores,
formatSkillScoresMarkdown,
isSuccessTurn,
appendSuccessPattern,
loadSuccessPatterns,
} from '../src/intelligence/skillScore';
import type { QueueItem } from '../src/intelligence/learningQueue';
import type { ReflectionRecord } from '../src/intelligence/reflectionStore';
const ITEM: QueueItem = {
id: 'need-market-research', topic: '시장조사 역량 보강', priority: 60, reason: '근거 없는 수행 다수',
status: 'approved', createdAt: 'a', updatedAt: 'a',
};
function mk(partial: Partial<ReflectionRecord>): ReflectionRecord {
return {
ts: '2026-06-11T10:00:00.000Z', taskId: 'meeting-minutes', taskLabel: '회의록',
confidenceScore: 80, confidenceBand: 'medium', missing: [], escalated: false,
criticIssues: null, promptPreview: '회의록 정리해줘', usedSources: ['회의기록.md'],
...partial,
};
}
describe('researchAgent', () => {
it('parseBrief — 잡설 섞인 JSON 파싱, 실패 시 fallback', () => {
const ok = parseBrief('계획: {"questions":["q1","q2"],"keywords":["k"],"sourceTypes":["공식 문서"]} 끝');
expect(ok!.questions).toEqual(['q1', 'q2']);
expect(parseBrief('JSON 없음')).toBeNull();
expect(fallbackBrief('주제').questions.length).toBeGreaterThan(0);
});
it('runResearch — 브리프→내부현황→초안→Validation 게이트 (출처 없음 = review)', async () => {
const calls: string[] = [];
const pkg = await runResearch({
item: ITEM,
fetchInternalRefs: async () => [{ title: '기존문서', content: '기존 시장조사 노트 내용', filePath: 'a.md' }],
callLlm: async (system) => {
calls.push(system.slice(0, 20));
if (system.includes('조사 계획')) {
return '{"questions":["시장 규모 출처는?"],"keywords":["시장조사"],"sourceTypes":["공식 통계"]}';
}
return '## 시장 규모\n일반적으로 통계청 자료를 쓴다 (모델 지식 — 추정, 출처 확인 필요)';
},
nowIso: '2026-06-11T00:00:00.000Z',
});
expect(pkg.brief.questions[0]).toContain('시장 규모');
expect(pkg.internalRefs.length).toBe(1);
expect(pkg.draft).toContain('추정');
// 출처가 없으므로 자동 수용 불가 — Permission Based Learning 게이트.
expect(pkg.validation.verdict).toBe('review');
expect(pkg.validation.checks.hasSource).toBe(false);
expect(calls.length).toBe(2);
});
it('LLM 전부 실패해도 fallback 브리프로 패키지 생성', async () => {
const pkg = await runResearch({
item: ITEM,
fetchInternalRefs: async () => [],
callLlm: async () => { throw new Error('down'); },
nowIso: '2026-06-11T00:00:00.000Z',
});
expect(pkg.brief.questions.length).toBeGreaterThan(0);
expect(pkg.draft).toContain('실패');
});
it('formatProposalMarkdown — 판정·브리프·다음 단계 포함', async () => {
const pkg = await runResearch({
item: ITEM, fetchInternalRefs: async () => [],
callLlm: async () => '{"questions":["q"],"keywords":["k"],"sourceTypes":["s"]}',
nowIso: '2026-06-11T00:00:00.000Z',
});
const md = formatProposalMarkdown(pkg, { dateStr: 'now', modelName: 'gemma' });
expect(md).toContain('검증 판정: review');
expect(md).toContain('/research');
expect(md).toContain('done 으로 변경');
});
});
describe('skillScore', () => {
it('확신도·충족률·비에스컬레이션 가중 합산 + 추세', () => {
const records = [
mk({ ts: '2026-06-01T10:00:00Z', confidenceScore: 50, missing: ['기한'], escalated: true }),
mk({ ts: '2026-06-02T10:00:00Z', confidenceScore: 55, missing: ['기한'] }),
mk({ ts: '2026-06-08T10:00:00Z', confidenceScore: 90, missing: [] }),
mk({ ts: '2026-06-09T10:00:00Z', confidenceScore: 95, missing: [] }),
];
const scores = computeSkillScores(records);
expect(scores.length).toBe(1);
expect(scores[0].trend).toBe('up');
expect(scores[0].secondHalf).toBeGreaterThan(scores[0].firstHalf);
const md = formatSkillScoresMarkdown(scores);
expect(md).toContain('상승');
});
it('표본 4건 미만이면 추세 flat', () => {
const scores = computeSkillScores([mk({}), mk({ confidenceScore: 20 })]);
expect(scores[0].trend).toBe('flat');
});
it('isSuccessTurn — 전 요소 충족 + 확신도 90+ 만', () => {
expect(isSuccessTurn(mk({ confidenceScore: 92, missing: [] }))).toBe(true);
expect(isSuccessTurn(mk({ confidenceScore: 92, missing: ['기한'] }))).toBe(false);
expect(isSuccessTurn(mk({ confidenceScore: 80, missing: [] }))).toBe(false);
});
it('append → load 성공 패턴 라운드트립 (성공 turn 만 저장)', () => {
const brain = fs.mkdtempSync(path.join(os.tmpdir(), 'astra-test-sp-'));
expect(appendSuccessPattern(brain, mk({ confidenceScore: 95, missing: [] }))).toBe(true);
expect(appendSuccessPattern(brain, mk({ confidenceScore: 50 }))).toBe(false);
const patterns = loadSuccessPatterns(brain);
expect(patterns.length).toBe(1);
expect(patterns[0].usedSources).toEqual(['회의기록.md']);
});
});