v2.2.268-291: /email 통합(브리핑·스캔·워처) + stocks 자동편입·매수알림 + 지식문서·메신저 중앙화

[/email — Gmail + 클래식 Outlook (신규 기능군)]
- 기본 명령: brief(PM 업무 허브 대시보드)·list·read·wikify·reply(Drafts만)·open(Outlook 창 열기)
- Gmail: 자체 OAuth gmail.readonly 스코프 추가 (재연결 1회 필요) / Outlook: COM(PowerShell), 전 계정(Store)·하위 폴더(120개 가드) 수집, To/CC 판별
- 브리핑: 상태 3분류(결정/회신/확인만)·P등급 영향·프로젝트 위험도·Today Focus·오늘 완료 예상·기간 명시 시 표시 범위 제한
- 영속 상태(email_state.json): 회신대기/공넘김 상태머신, 창 밖 미회신 보존, done old N 일괄정리
- 워처: 주기 자동 수집 + 미회신 임계 알림(기본 끔) + 데일리 브리핑 '답할 이메일' 섹션
- 전체 스캔(/email scan): 자동 위키화(회차 15건·레지스트리·파일삭제 감지 재생성)·진행 표시(streamProgress)
- 자동발신 필터 확장(Confluence·AWS·광고·[Notification]) + 라벨 오타 후처리(polishBriefLabels)

[/stocks]
- discover 자동 편입(stocks.json) + 매수권장가 224일선 자동 산출
- 매수사정권 진입 전이 감지 → 텔레그램 1회 알림 (직전신호 추적)
- 버그 수정: '미충족'이 .includes('충족')에 오탐되던 필터 판정

[지식 문서 파이프라인 통일 (wikiFormat/wikiSave)]
- PC 로컬 직접 저장 (NAS 볼륨 미아 문제 해소) — 설정 폴더 → 두뇌 → 워크스페이스
- 이메일 전용 Email_Wiki 분리(email.wikifySavePath) + 봉투(frontmatter) 자동 통일
- 정본 프롬프트 단일 빌더(buildKnowledgeWikiPrompt) — web/email 래퍼화, 템플릿 fetch 일원화
- 이메일 문서 시점 3중 방어: 기준시점 헤더·제목=메일 최신일·날짜 없는 현재형 금지
- docs/WIKI_DOC_PIPELINE.md 개발 규약

[텔레그램 메신저 중앙화 (messageFormat)]
- 단일 렌더러 renderTelegram — 5개 발신 빌더(주식보고·매수시그널·Top5·데일리·미회신) 통일
- 주식 보고 시각 하드코딩 제거(messenger.stocksReportTimes CSV) + 알림 토글 2종
- 설정 패널 '메신저' 탭 신설 · '이메일' 토글 섹션(Gmail/Outlook on/off)

테스트 803개 통과 (이메일 상태머신·CC판별·포맷·매수전이·봉투 등 60+ 신규)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 18:57:12 +09:00
parent 92d81e38ce
commit fa397d7fa1
44 changed files with 3635 additions and 174 deletions
+64
View File
@@ -0,0 +1,64 @@
/**
* v2.2.278 — 위키 문서 포맷 통일 (봉투 + 공용 빌더).
*/
import { ensureFrontmatter, hasFrontmatter, slugifyId, buildKnowledgeWikiPrompt } from '../src/features/datacollect/wikiFormat';
import { buildWikifyPrompt, buildEmailCanonicalWikifyPrompt } from '../src/features/datacollect/prompts/wikifyPrompt';
describe('ensureFrontmatter — 봉투 통일', () => {
test('frontmatter 없는 보고서형 문서에 정본 봉투 부착', () => {
const out = ensureFrontmatter('회의록 주간회의 2026-07-03', '# 회의록\n내용…', {
docType: 'meeting', sources: ['transcript.txt'],
});
expect(out.startsWith('---')).toBe(true);
expect(out).toContain('title: "회의록 주간회의 2026-07-03"');
expect(out).toContain('category: "meeting"');
expect(out).toContain('tags: ["meeting", "astra"]');
expect(out).toContain('raw_sources: ["transcript.txt"]');
expect(out).toContain('# 회의록'); // 본문 보존
expect(out).toContain('## 📝 변경 이력');
});
test('이미 frontmatter 있는 문서(정본 프롬프트 산출물)는 그대로', () => {
const doc = '---\nid: x\n---\n본문';
expect(ensureFrontmatter('t', doc, { docType: 'wikify' })).toBe(doc);
});
test('코드펜스로 감싼 frontmatter 도 인식 (LLM 출력 변형 대응)', () => {
expect(hasFrontmatter('```markdown\n---\nid: x\n---\n```')).toBe(true);
expect(hasFrontmatter('# 그냥 제목')).toBe(false);
});
test('slugifyId — 한글 유지·특수문자 제거·80자 캡', () => {
expect(slugifyId('회의록 A/B 테스트!', 'x')).toBe('회의록-ab-테스트');
expect(slugifyId('', 'fallback')).toBe('fallback');
});
});
describe('buildKnowledgeWikiPrompt — 단일 정본 빌더', () => {
test('kind별 규칙 주입 + 정본 골격', () => {
const p = buildKnowledgeWikiPrompt({
kind: 'slack', topic: '배포 논의', body: 'BODY',
meta: { : '#dev' }, sourceRef: 'slack:#dev',
extraRules: ['슬랙 특수 규칙 X'], trustHint: '내부 교신이므로 A~B 로',
}, null);
expect(p).toContain("주제: '배포 논의'");
expect(p).toContain('5. 슬랙 특수 규칙 X'); // extraRules 번호 이어붙임
expect(p).toContain('6. 이 문서의 출처는 slack'); // trust 규칙이 그 다음 번호
expect(p).toContain('- 채널: #dev');
expect(p).toContain('## 🎯 한 줄 통찰');
expect(p).toContain('## 🔗 지식 그래프');
expect(p).toContain('raw_sources: ["slack:#dev"]');
});
test('web·email 래퍼가 같은 정본 골격을 공유 (파편화 종료)', () => {
const web = buildWikifyPrompt({ url: 'https://x.com', title: 'T', text: 'B' }, '', null);
const email = buildEmailCanonicalWikifyPrompt('T', 'B', {}, null);
for (const marker of ['[필수 규칙]', '[소스 메타]', '[소스 본문]', '## 🎯 한 줄 통찰', '## 📚 출처', 'P-Reinforce v3.1']) {
expect(web).toContain(marker);
expect(email).toContain(marker);
}
// 종류별 특수 규칙은 각자 유지
expect(web).toContain('JSON Schema');
expect(email).toContain('누가·언제·무엇을');
});
});