v2.2.299~307: 아키텍처 수렴 + 채팅 정리 + 벤치마킹 강화 + Claude 구독 엔진

- v2.2.299 아키텍처 수렴: coreChat 통일(엔진 휴리스틱 3벌 제거), 기업 모드
  검색 오케스트레이터 승격, lib/execUtil(실행 래퍼 6곳·Python 탐지 3벌 단일화),
  lib/kstSchedule(워처 4개 nowInKst 통합), estimateTokens 통합, 설정 접근 규칙 명문화
- v2.2.300 채팅 화면 정리: LiveReasoningFilter(스트리밍 중 <think>/Harmony 추론
  토큰 단위 차단), 확신도·검토요청 footer 기본 숨김(계산·Reflection 은 유지)
- v2.2.301 문맥·의도 이해: [답변 전 이해 원칙] 상시 주입, 워크플로우 의도 브리핑,
  Report QA 루프(규칙 레지스트리+실측치+회귀 게이트, 블로그_v3 개념 이식)
- v2.2.302 /benchmark 비즈니스 렌즈(가격·수익·운영)+빌드 프롬프트 모드+QA 연계
- v2.2.303 handoff 모드(측정치 무손실 인수인계 문서)+/claude(Claude Code 터미널 위임)
- v2.2.304 이식성: 지식 경로 두뇌-상대 규약(pickWikiDir 상대 해석), 이사 체크리스트
- v2.2.305 Claude 구독 엔진: claude: 프로바이더(CLI 위임, 모델 드롭다운 자동 노출,
  coreChat 지원 — 워크플로우·QA도 구독 모델 가능)
- v2.2.306 Tone Guard: AI 상투어 금지 레지스트리(상담사 화법 실사례 8종+대조 예시)
- v2.2.307 /benchmark 레이아웃 골격(sectionRoles 결정론 분석, 롤링 배너 즉답),
  파트별 실패 격리, 합성 타임아웃 120→300초

검증: tsc 무오류 + jest 888 통과 + esbuild 정상

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 21:02:15 +09:00
parent 98d533f045
commit 47b3b9f93a
66 changed files with 2421 additions and 459 deletions
+73
View File
@@ -0,0 +1,73 @@
import { flattenClaudePrompt, buildClaudeCliArgs, extractFromStreamJson, listClaudeCodeModels } from '../src/features/providers/claudeCode';
import { parseModelPrefix, makeModelId } from '../src/features/providers/types';
describe('claude: 프리픽스 라우팅', () => {
test('claude:sonnet → claude-code 프로바이더로 라우팅', () => {
expect(parseModelPrefix('claude:sonnet')).toEqual({ provider: 'claude-code', model: 'sonnet' });
});
test('makeModelId 는 프리픽스 테이블을 따른다 (claude-code → claude:)', () => {
expect(makeModelId('claude-code', 'sonnet')).toBe('claude:sonnet');
expect(makeModelId('anthropic', 'claude-opus-4-8')).toBe('anthropic:claude-opus-4-8');
});
test('로컬 모델은 여전히 null (로컬 엔진 경로)', () => {
expect(parseModelPrefix('gemma4:e2b')).toBeNull();
});
});
describe('flattenClaudePrompt — 메시지 평탄화', () => {
test('시스템 블록·화자 라벨·도구 금지 지시를 포함한다', () => {
const p = flattenClaudePrompt([
{ role: 'system', content: '너는 아스트라다.' },
{ role: 'user', content: '안녕' },
{ role: 'assistant', content: '반갑습니다' },
{ role: 'user', content: '이어서 질문' },
]);
expect(p).toContain('<system_instructions>\n너는 아스트라다.\n</system_instructions>');
expect(p).toContain('도구를 사용하지 말고');
expect(p).toContain('[사용자]\n안녕');
expect(p).toContain('[아스트라]\n반갑습니다');
expect(p.indexOf('[사용자]\n안녕')).toBeLessThan(p.indexOf('[사용자]\n이어서 질문'));
});
test('시스템 없는 단일 user 도 동작', () => {
expect(flattenClaudePrompt([{ role: 'user', content: 'hi' }])).toBe('[사용자]\nhi');
});
});
describe('buildClaudeCliArgs', () => {
test('headless stream-json + 모델 지정', () => {
expect(buildClaudeCliArgs('sonnet')).toEqual(['-p', '--output-format', 'stream-json', '--verbose', '--model', 'sonnet']);
});
test('모델 비면 --model 생략 (CLI 기본 모델)', () => {
expect(buildClaudeCliArgs('')).toEqual(['-p', '--output-format', 'stream-json', '--verbose']);
});
});
describe('extractFromStreamJson — CLI 출력 파싱', () => {
test('assistant 이벤트의 text 블록을 이어붙인다', () => {
const lines = [
'{"type":"system","subtype":"init"}',
'{"type":"assistant","message":{"content":[{"type":"text","text":"안녕"},{"type":"text","text":"하세요"}]}}',
'{"type":"result","subtype":"success","result":"안녕하세요"}',
];
expect(extractFromStreamJson(lines)).toEqual({ text: '안녕하세요', error: '' });
});
test('assistant 이벤트가 없으면 result 본문을 사용 (버전 차이 대응)', () => {
const lines = ['{"type":"result","subtype":"success","result":"본문"}'];
expect(extractFromStreamJson(lines).text).toBe('본문');
});
test('에러 result 는 error 로 추출', () => {
const lines = ['{"type":"result","subtype":"error_during_execution","is_error":true,"result":"Invalid API key"}'];
const r = extractFromStreamJson(lines);
expect(r.text).toBe('');
expect(r.error).toContain('Invalid API key');
});
test('JSON 아닌 라인·깨진 라인은 무시', () => {
expect(extractFromStreamJson(['잡음', '{broken', ''])).toEqual({ text: '', error: '' });
});
});
describe('listClaudeCodeModels', () => {
test('구독 별칭 3종', () => {
expect(listClaudeCodeModels()).toEqual(['sonnet', 'opus', 'haiku']);
});
});