Files
connectai/tests/secondBrainTrace.test.ts
T

219 lines
11 KiB
TypeScript

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, 'UX'), { recursive: true });
fs.writeFileSync(
path.join(brainRoot, 'UX', 'Customer Journey Virtual Store.md'),
[
'# Customer Journey Virtual Store',
'',
'Customer-facing virtual stores should connect spatial experience to product discovery, product understanding, and purchase conversion.',
'Stakeholder approval often depends on requirement fit, business value, and acceptance criteria rather than visual novelty alone.'
].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('<details>');
expect(markdown).toContain('<summary>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('<details>');
});
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 });
}
});
it('prioritizes UX and business documents for approval and customer-experience questions', () => {
const trace = buildSecondBrainTrace(
'롯데 이노베이트가 고객 대상 버추얼 웹스토어에서 상품 중심이 아니라 공간 중심 개발 방향을 승인할 가능성이 있을까?',
brainRoot,
{ force: true }
);
expect(trace.queryIntent).toBe('ux-business');
expect(trace.retrievalQuery).toContain('customer journey');
expect(trace.retrievalQuery).toContain('approval');
expect(trace.retrievedDocuments[0].path).toContain('Customer Journey Virtual Store.md');
expect(trace.retrievedDocuments[0].path).not.toContain('API Gateway.md');
expect(renderSecondBrainTraceContext(trace)).toContain('Approval likelihood is an inference');
});
});