Files
connectai/tests/lessonNetwork.test.ts
T
koriweb 925d91a4e5 feat(growth): 지식 재구성 — 충돌 통합 초안 + A-MEM 레슨 네트워크 (v2.2.228)
ASTRA 자기 제안(검증 통과분) 1순위 구현: 충돌을 '감지'에서 '재구성'으로.

[충돌 → 통합 초안 (사람 승인 대기)]
- conflictScan: 모순 감지 시 LLM이 통합 초안 생성 → .astra/growth/reconcile/
  (런당 ≤3건). 신뢰 권고 우선 쪽 기준 + 타방의 유효 정보 보존 + 판단 불가
  사실은 "(확인 필요: A는 X, B는 Y)" 병기 + 출처 표기 강제.
- 자동 반영 절대 없음 — 초안 머리에 명기, 승인 시 사람이 직접 반영 (status:
  pending-review). 거부 = 파일 삭제.

[A-MEM 레슨 네트워크 (NeurIPS 2025 이식, deep research 2순위)]
- lessonNetwork.ts: 새 레슨 저장 시 기존 레슨과 토큰 자카드 유사도 상위 3개를
  "## 관련 레슨" [[위키링크]]로 연결 + 기존 레슨에 백링크(역방향 갱신 —
  memory evolution). LLM 호출 0 — 캡처 경로 지연 없음. 멱등(재실행 안전).
- 연결 지점: Correction Loop 자동 레슨 + 수동 레슨 생성(Astra: New Lesson).
  고립된 카드 모음 → 상호 연결 네트워크: "같은 종류의 실수" 패턴이 파일
  수준에서 보이고 RAG 위키링크로 함께 검색됨.

테스트 6건 추가 (유사도·링크 멱등·양방향·초안 형식). 전체 588 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:25:28 +09:00

85 lines
4.3 KiB
TypeScript

/**
* 레슨 네트워크(A-MEM 이식) + 통합 초안 형식 — 순수 로직 테스트.
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { lessonSimilarity, addRelatedLink, linkRelatedLessons } from '../src/intelligence/lessonNetwork';
import { reconcileDraftMarkdown, type ConflictFinding } from '../src/features/growth/conflictScan';
import { tokenize } from '../src/retrieval/scoring';
const tmpBrain = () => fs.mkdtempSync(path.join(os.tmpdir(), 'astra-net-'));
function mkLesson(brain: string, name: string, body: string): string {
const dir = path.join(brain, 'lessons');
fs.mkdirSync(dir, { recursive: true });
const fp = path.join(dir, `${name}.md`);
fs.writeFileSync(fp, body, 'utf8');
return fp;
}
describe('lessonSimilarity', () => {
test('겹치는 토큰 비율 — 동일 주제 > 무관 주제', () => {
const a = tokenize('회의 날짜를 잘못 기억해 캘린더 등록 오류 발생');
const b = tokenize('회의 일정 등록 시 날짜 확인 누락으로 오류');
const c = tokenize('파이썬 가상환경 의존성 충돌 해결 방법');
expect(lessonSimilarity(a, b)).toBeGreaterThan(lessonSimilarity(a, c));
expect(lessonSimilarity(a, a)).toBe(1);
expect(lessonSimilarity([], a)).toBe(0);
});
});
describe('addRelatedLink', () => {
test('섹션 없으면 생성, 있으면 append, 중복은 멱등', () => {
let c = '# Lesson: X\n\n## Fix\n내용\n';
c = addRelatedLink(c, '레슨A');
expect(c).toContain('## 관련 레슨\n- [[레슨A]]');
c = addRelatedLink(c, '레슨B');
expect(c).toContain('- [[레슨A]]\n- [[레슨B]]');
const again = addRelatedLink(c, '레슨A');
expect(again).toBe(c); // 멱등
});
test('관련 레슨 섹션 뒤에 다른 헤딩이 있어도 섹션 안에 삽입', () => {
const c = '# L\n\n## 관련 레슨\n- [[기존]]\n\n## Applies To\n- 태그\n';
const out = addRelatedLink(c, '신규');
expect(out.indexOf('- [[신규]]')).toBeLessThan(out.indexOf('## Applies To'));
});
});
describe('linkRelatedLessons — 상호 링크 + 역방향 갱신', () => {
test('유사 레슨과 양방향 링크 생성', () => {
const brain = tmpBrain();
const oldFp = mkLesson(brain, 'old-meeting-date', '# Lesson: 회의 날짜 오류\n\n## Mistake / Risk\n회의 날짜를 잘못 기억해 캘린더 등록 오류 발생\n');
mkLesson(brain, 'unrelated-python', '# Lesson: 파이썬 환경\n\n## Mistake / Risk\n파이썬 가상환경 의존성 충돌 npm 빌드 도구 체인 별개 주제\n');
const newFp = mkLesson(brain, 'new-meeting-date', '# Lesson: 회의 일정 재발\n\n## Mistake / Risk\n회의 일정 등록 시 날짜 확인 누락 캘린더 오류\n');
const linked = linkRelatedLessons(brain, newFp);
expect(linked).toBeGreaterThanOrEqual(1);
expect(fs.readFileSync(newFp, 'utf8')).toContain('[[old-meeting-date]]');
expect(fs.readFileSync(oldFp, 'utf8')).toContain('[[new-meeting-date]]'); // 역방향
});
test('유사 레슨 없으면 0 (파일 미변경)', () => {
const brain = tmpBrain();
mkLesson(brain, 'a', '# Lesson: 완전히 다른 알파 베타 감마 주제\n');
const newFp = mkLesson(brain, 'b', '# Lesson: 전혀 무관한 델타 입실론 제타\n');
const before = fs.readFileSync(newFp, 'utf8');
expect(linkRelatedLessons(brain, newFp)).toBe(0);
expect(fs.readFileSync(newFp, 'utf8')).toBe(before);
});
});
describe('reconcileDraftMarkdown', () => {
test('frontmatter + 비자동반영 고지 + 본문', () => {
const f: ConflictFinding = {
newDoc: 'AI_and_ML/신규.md', existingDoc: 'AI_and_ML/기존.md',
summary: '버전 수치 모순', recommend: '권고: **신규 우선** (S·1.0 vs A·0.8)',
};
const md = reconcileDraftMarkdown(f, '## 통합 내용\n…', '2026-06-12T00:00:00Z');
expect(md).toMatch(/^---\ntype: reconcile-draft/);
expect(md).toContain('status: pending-review');
expect(md).toContain('자동 반영되지 않습니다');
expect(md).toContain('버전 수치 모순');
expect(md).toContain('신규 우선');
});
});