feat(retrieval): 청크 검색 기본 켬(+62.5%p recall@1) + 확신도 전역화 (v2.2.218)

P1 — 섹션 청크 검색 기본 활성화:
- 골든셋 24질의 A/B 측정: 파일 단위 → 섹션 청크에서
  recall@1 12.5%→75.0% · recall@3 33.3%→83.3% · recall@5 37.5%→87.5%
  · MRR 0.217→0.802. 18질의 개선·악화 0건.
- Phase 1-가 구현은 완성돼 있었으나 chunkLevelRetrieval 기본값이 false 라
  실전 채팅이 열등한 파일 모드로 동작 — package.json·config 기본값 true 로.
- tests/retrievalEvalCompare.test.ts: 환경변수(ASTRA_EVAL_BRAIN) 게이트형
  A/B 회귀 측정 도구 (평소 skip — CI/패키징 무영향).

P2 — 확신도 전역화 (/meet 원칙을 모든 대화로):
- memoryContext 에 [GROUNDING] 블록 — 두뇌 근거 강도(강/보통/약)를 점수로
  평가해 답변 정책 주입: 약함 → "⚠️ 두뇌 근거 약함" 표기+단정 금지,
  강함 → 근거 문서 제목 인용, 보통 → 사실/추론 구분 서술.

P3 — 회의 용어집 자동화 + 출력 위생:
- /meet 실행마다 담당자 이름·사용자 메타데이터 용어를 .astra/meet_glossary.json
  에 누적, 다음 실행 때 자동 주입 (STT 보정 용어집 — 반복 회의 표기 일관성).
- selfIdentity 블록에 한·영 혼합 깨진 표기 금지 규칙 (전 대화, 무비용).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 18:13:11 +09:00
parent 92c92da090
commit c42c66a3fc
8 changed files with 174 additions and 7 deletions
@@ -251,3 +251,38 @@ export function renderPendingQuestion(p: PendingFile): string {
export function logMeetRegistration(event: string, data: Record<string, unknown>): void {
logInfo(`/meet 등록 게이트: ${event}`, data);
}
// ── 회의 용어집 (반복 회의의 STT 보정 정확도용) ─────────────────────────────
// meetPrompt 는 메타데이터를 "용어집 역할"로 쓴다 — 매번 수동 입력하는 대신,
// 이전 /meet 실행에서 나온 인명(담당)·사용자 입력 메타데이터 용어를 워크스페이스에
// 누적하고 다음 실행 때 자동 주입한다. (.astra/meet_glossary.json)
const GLOSSARY_REL = 'meet_glossary.json';
const GLOSSARY_MAX = 120;
type Glossary = { terms: string[]; updatedAt: string };
export function loadGlossaryTerms(): string[] {
return (readJson<Glossary>(GLOSSARY_REL)?.terms || []).filter(t => typeof t === 'string' && t.trim());
}
/** 담당자 이름·메타데이터에서 뽑은 용어를 용어집에 누적 (중복 제거, 상한 유지). */
export function updateGlossary(newTerms: string[]): void {
const cleaned = newTerms
.map(t => (t || '').trim())
.filter(t => t.length >= 2 && t.length <= 30 && !/미지정|없음|확인|불명/.test(t));
if (!cleaned.length) return;
const cur = loadGlossaryTerms();
const set = new Set(cur);
for (const t of cleaned) set.add(t);
// 상한 초과 시 오래된 것부터 제거 (Set 삽입 순서 = 누적 순서)
const all = [...set];
const terms = all.length > GLOSSARY_MAX ? all.slice(all.length - GLOSSARY_MAX) : all;
writeJson(GLOSSARY_REL, { terms, updatedAt: new Date().toISOString() } satisfies Glossary);
}
/** 사용자 메타데이터 입력에서 용어 후보 추출 — 쉼표/슬래시/공백 구분 토큰 중 고유명사형. */
export function extractGlossaryCandidates(metadata: string): string[] {
return (metadata || '')
.split(/[,/·;\n]+/)
.map(t => t.replace(/^[\s:\-–·]+|[\s:\-–·]+$/g, ''))
.filter(t => t.length >= 2 && t.length <= 30 && !/^\d+$/.test(t));
}