import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { buildSecondBrainTrace, enforceProjectClaimPolicyInAnswer, renderSecondBrainTraceContext, renderSecondBrainTraceMarkdown } from '../src/features/secondBrainTrace'; describe('Second Brain Trace', () => { let brainRoot: string; beforeEach(() => { brainRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'second-brain-trace-')); fs.mkdirSync(path.join(brainRoot, 'records', 'ProjectChronicle', 'decisions'), { recursive: true }); fs.writeFileSync( path.join(brainRoot, 'records', 'ProjectChronicle', 'decisions', 'ADR-0002-low-dependency-design.md'), [ '# ADR-0002 Low Dependency Design', '', 'Project Chronicle Guard should start with Markdown files and an independent module.', 'Vector DB and relational DB are later expansion options, not MVP dependencies.' ].join('\n'), 'utf8' ); fs.writeFileSync( path.join(brainRoot, 'general-note.md'), '# General Note\n\nThis unrelated note talks about coffee and weather.', 'utf8' ); fs.mkdirSync(path.join(brainRoot, '02_Architecture_Principles'), { recursive: true }); fs.writeFileSync( path.join(brainRoot, '02_Architecture_Principles', 'API Gateway.md'), [ '# API Gateway', '', 'General Knowledge: API Gateway can route requests in a microservice architecture.', 'This document is not evidence that any current project implements API Gateway.' ].join('\n'), 'utf8' ); fs.mkdirSync(path.join(brainRoot, '00_Raw', 'conversations'), { recursive: true }); fs.writeFileSync( path.join(brainRoot, '00_Raw', 'conversations', '2026-05-01.md'), [ '# Raw Conversation', '', 'dependency complexity schema drift documentation gap recommendations', 'This is a noisy transcript and should not be selected before curated records.' ].join('\n'), 'utf8' ); fs.writeFileSync( path.join(brainRoot, 'Index_692.md'), '# Index\n\ndependency complexity schema drift documentation gap' ); }); afterEach(() => { fs.rmSync(brainRoot, { recursive: true, force: true }); }); it('retrieves and marks relevant Second Brain notes for project-specific questions', () => { const trace = buildSecondBrainTrace('Project Chronicle Guard MVP에서 Vector DB는 어떻게 다뤄야 해?', brainRoot); expect(trace.shouldUseSecondBrain).toBe(true); expect(trace.secondBrainUsed).toBe(true); expect(trace.retrievedDocuments[0].path).toContain('ADR-0002-low-dependency-design.md'); expect(trace.retrievedDocuments[0].usedInAnswer).toBe(true); expect(trace.retrievedDocuments[0].selectedForAnswerContext).toBe(true); expect(trace.retrievedDocuments[0].sourceType).toBe('User Decision'); expect(trace.retrievedDocuments[0].canSupportProjectClaim).toBe(true); expect(trace.groundingScore).toBeGreaterThan(0); }); it('renders user-facing markdown and debug JSON', () => { const trace = buildSecondBrainTrace('Second Brain을 참고해서 low dependency 원칙 알려줘', brainRoot, { force: true }); const markdown = renderSecondBrainTraceMarkdown(trace, true); const context = renderSecondBrainTraceContext(trace); expect(markdown).toContain('
'); expect(markdown).toContain('2nd Brain Trace: 사용함'); expect(markdown).toContain('## 2nd Brain 사용 여부'); expect(markdown).toContain('## 답변 컨텍스트로 선택된 2nd Brain 문서'); expect(markdown).toContain('## Second Brain Debug JSON'); expect(context).toContain('[SECOND BRAIN TRACE]'); expect(context).toContain('Retrieval query:'); expect(context).toContain('Do not imitate dramatic wording'); expect(context).toContain('No Evidence, No Project Claim'); }); it('explains when Second Brain is not needed', () => { const trace = buildSecondBrainTrace('오늘 날짜가 뭐야?', brainRoot); expect(trace.shouldUseSecondBrain).toBe(false); expect(trace.secondBrainUsed).toBe(false); expect(renderSecondBrainTraceMarkdown(trace)).toContain('사용하지 않음'); expect(renderSecondBrainTraceMarkdown(trace)).toContain('
'); }); it('prefers curated notes over raw conversations and index files', () => { const trace = buildSecondBrainTrace( 'dependency complexity schema drift documentation gap 문제 대응 가이드', brainRoot ); expect(trace.retrievedDocuments[0].path).toContain('ADR-0002-low-dependency-design.md'); expect(trace.retrievedDocuments.find((doc) => doc.path.includes('00_Raw'))).toBeUndefined(); expect(trace.retrievedDocuments[0].path).not.toContain('Index_692.md'); }); it('classifies general architecture notes as unable to support project implementation claims', () => { const trace = buildSecondBrainTrace( '현재 프로젝트는 API Gateway 라우팅 구조를 갖추고 있어?', brainRoot, { force: true } ); const apiGateway = trace.retrievedDocuments.find((doc) => doc.path.includes('API Gateway.md')); expect(apiGateway).toBeDefined(); expect(apiGateway?.sourceType).toBe('General Knowledge'); expect(apiGateway?.canSupportProjectClaim).toBe(false); expect(apiGateway?.warning).toContain('실제 구현 근거가 아닙니다'); const markdown = renderSecondBrainTraceMarkdown(trace, true); const context = renderSecondBrainTraceContext(trace); expect(trace.projectClaimPolicy).not.toBe('allow'); expect(markdown).toContain('"canSupportProjectClaim": false'); expect(context).toContain('No Evidence, No Project Claim'); expect(context).toContain('현재 정보만으로는 기술 구조를 판단할 수 없습니다'); expect(context).toContain('아키텍처는 유연합니다'); }); it('uses general-only policy when selected notes cannot support project claims', () => { const generalOnlyRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'second-brain-general-only-')); try { fs.mkdirSync(path.join(generalOnlyRoot, '02_Architecture_Principles'), { recursive: true }); fs.writeFileSync( path.join(generalOnlyRoot, '02_Architecture_Principles', 'API Gateway.md'), [ '# API Gateway', '', 'General Knowledge: API Gateway can route requests in a microservice architecture.', 'This is a concept note, not current project evidence.' ].join('\n'), 'utf8' ); const trace = buildSecondBrainTrace( '현재 프로젝트는 API Gateway 라우팅 구조를 갖추고 있어?', generalOnlyRoot, { force: true } ); expect(trace.projectClaimPolicy).toBe('general-only'); expect(renderSecondBrainTraceContext(trace)).toContain('STRICT RULE'); } finally { fs.rmSync(generalOnlyRoot, { recursive: true, force: true }); } }); it('removes unsupported technical structure claims from final answers under general-only policy', () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'second-brain-policy-')); try { fs.mkdirSync(path.join(root, '02_Architecture_Principles'), { recursive: true }); fs.writeFileSync( path.join(root, '02_Architecture_Principles', 'API Gateway.md'), '# API Gateway\n\nGeneral Knowledge: API Gateway can route requests.', 'utf8' ); const trace = buildSecondBrainTrace('현재 프로젝트는 API Gateway 라우팅 구조를 갖추고 있어?', root, { force: true }); const answer = [ '결론은 간단합니다. 현재 개발 방향은 기술적 기반 면에서는 안정적입니다.', '', '아키텍처는 유연하나 구매 전환 시나리오를 보완해야 합니다.', '모듈화된 구조는 향후 기능 추가 시 유연성을 제공합니다.', '', '다만 UX 관점에서는 공간 경험과 상품 탐색 연결을 확인해야 합니다.' ].join('\n'); const sanitized = enforceProjectClaimPolicyInAnswer(answer, trace); expect(sanitized).toContain('현재 정보만으로는 기술 구조를 판단할 수 없습니다'); expect(sanitized).not.toContain('기술적 기반 면에서는 안정적'); expect(sanitized).not.toContain('아키텍처는 유연'); expect(sanitized).not.toContain('모듈화된 구조'); expect(sanitized).toContain('UX 관점'); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); });