2afd1ac589
신뢰성 코어 (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>
123 lines
5.7 KiB
TypeScript
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']);
|
|
});
|
|
});
|