refactor: v2.2.195-201 — slashRouter god-file 해체 (–95%) + 인프라 5개 추출
아키텍처 감사 결과 HIGH 2건 + MED 2건 + LOW 1건 — 7 라운드 정리 시리즈.
기능 변경 없음, 순수 구조 정리.
**slashRouter.ts: 4,174 → 201줄 (–3,973, –95%)**
**agent.ts: 1,617 → 1,551줄 (–66, –4%)**
v2.2.195: eventSourcedStore + SystemPromptBlock registry
- createEventStore<E>(opts) — 4 store (customers/hire/runway/feedback) I/O 240줄 중복 제거
- _turnCtx 5 named string field → 1 Map<string, string> (새 verification block 추가 25곳→1곳)
- buildAstraModeSystemPrompt: 5 ternary gate + 5 위치 → 1 for-loop join
v2.2.196: trackers cluster split
- src/features/teamops/handlers/_shared.ts (fmtKrw/parseAmount/daysUntil/parseTaskOwner/stageEmoji/STAGE_ORDER/TERMINAL_STAGES)
- src/features/teamops/handlers/trackers.ts (runway/customers/hire)
- src/features/teamops/handlers/index.ts (barrel)
- extension.ts 에 side-effect import (순환 import 회피)
v2.2.197: mtimeFileCache + PostAnswerHook registry
- src/lib/mtimeFileCache.ts — createMtimeFileCache<T>(name, parse) (terminologyBlock + termValidator 2-cache invariant 자동화)
- src/agent/postAnswerHooks/{types,index}.ts — Devil/SelfCheck/TermValidator 3 _maybeX method → 1 runPostAnswerHooks(ctx) loop
- agent.ts –66줄
v2.2.198: dashboards cluster split
- src/features/teamops/handlers/dashboards.ts (morning/evening/cohort/weekly)
v2.2.199: coordination + communication clusters split
- src/features/teamops/handlers/coordination.ts (task/decisions/onesie/blocked/standup)
- src/features/teamops/handlers/communication.ts (draft/feedback)
- callLmSynthesis export 노출 (communication 이 사용)
- 옛 parseTaskOwner local 정의 삭제 (_shared.ts 사용)
v2.2.200: system cluster split
- src/features/system/handlers.ts (memory/glossary/help)
v2.2.201: datacollect cluster split + LLM 인프라 추출
- src/features/datacollect/handlers.ts (research/benchmark/youtube/blog/wikify/meet)
- src/features/datacollect/llm.ts (callLmSynthesis + repairKoreanGlitches + bridgeErrorRemedy)
- slashRouter import 4개로 축소: vscode/logInfo/getBridgeBaseUrl/bridgeErrorRemedy
**최종 slashRouter (201줄):**
- REGISTRY Map + registerSlashCommand/listSlashCommands/isSlashCommand
- handleSlashCommand (dispatcher + 에러 처리)
- Webview interface + chunk helper
- getRecentSlashCommands ring buffer (actionability scoring 용)
**미래 부담 감소 metrics:**
- 새 슬래시 명령: god-file 끝에 함수 + register → 1 파일 + 1 register call
- 새 verification block: 5곳 편집 → 1 set call
- 새 event store: 60줄 boilerplate → createEventStore 한 줄
- 새 post-answer hook: 3 step → 1 push
- 새 mtime cache: Map + invariant 관리 → createMtimeFileCache 한 줄
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Datacollect LLM 호출 인프라 — bridge `/api/lm` 프록시 통해 OpenAI 호환 chat
|
||||
* completion 단발 호출.
|
||||
*
|
||||
* v2.2.201 에서 slashRouter.ts 에서 분리. 옛 위치는 slashRouter.callLmSynthesis
|
||||
* 였으나 datacollect handlers + teamops/communication 양쪽이 import 하므로
|
||||
* 별도 인프라 모듈로.
|
||||
*
|
||||
* `repairKoreanGlitches` 는 callLmSynthesis 출력 위생용 — 모델이 한·영 혼합
|
||||
* 토큰 깨짐 (예: "핵ess") 을 뱉으면 LLM 1회 추가 호출로 교정.
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { bridgeFetch, BRIDGE_API } from './bridgeClient';
|
||||
|
||||
/**
|
||||
* Bridge `/api/lm` 프록시로 OpenAI 호환 chat completion 1회 호출.
|
||||
* LLM 서버/모델은 Astra 설정(g1nation.ollamaUrl / defaultModel)을 사용한다.
|
||||
*/
|
||||
export async function callLmSynthesis(prompt: string, systemPrompt?: string): Promise<string> {
|
||||
const cfg = vscode.workspace.getConfiguration('g1nation');
|
||||
const lmUrl = (cfg.get<string>('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434').replace(/\/$/, '');
|
||||
const model = (cfg.get<string>('defaultModel', '') || 'gemma4:e2b').trim();
|
||||
const temperature = Math.max(0, Math.min(2, cfg.get<number>('datacollectSynthesisTemperature', 0.1) ?? 0.1));
|
||||
const baseSys = systemPrompt
|
||||
|| '당신은 시니어 UX/UI 분석가이자 프론트엔드 아키텍트다. 모든 보고서는 한국어로, 제공된 JSON 스캔 데이터의 구체적 수치를 인용해 작성한다. 원본 레퍼런스 사이트를 처음부터 다시 만들기 위한 명세를 작성하는 것이 미션이며, 다른 서비스로 재해석·확장하지 않는다.';
|
||||
const sys = baseSys + '\n\n[출력 위생 규칙 — 반드시 준수]\n'
|
||||
+ '- 자연스러운 한국어로 작성하고, 한 단어 안에 한글과 영문 알파벳을 섞지 마시오 ("결ently", "인orp" 같은 깨진 합성 표기 절대 금지).\n'
|
||||
+ '- 외래어·기술 용어는 완전한 한글 표기 또는 완전한 영문 단어 중 하나로 일관되게 쓰시오.\n'
|
||||
+ '- [Self-Reflector Check], Consistency/Completeness/Accuracy 같은 내부 검증·체크 로그 블록을 출력에 절대 포함하지 마시오. 최종 사용자 결과물만 출력하시오.';
|
||||
const res = await bridgeFetch<any>(BRIDGE_API.lm.proxy, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
url: `${lmUrl}/v1/chat/completions`,
|
||||
payload: {
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: sys },
|
||||
{ role: 'user', content: prompt },
|
||||
],
|
||||
temperature,
|
||||
top_p: 0.85,
|
||||
top_k: 20,
|
||||
repeat_penalty: 1.1,
|
||||
},
|
||||
}),
|
||||
}, { timeoutMs: 120_000 });
|
||||
const content = res?.choices?.[0]?.message?.content
|
||||
?? res?.choices?.[0]?.text
|
||||
?? res?.answer
|
||||
?? res?.response
|
||||
?? '';
|
||||
let out = String(content)
|
||||
.replace(/\n*(?:```[\w]*\s*)?\[Self-Reflector Check\][\s\S]*$/i, '')
|
||||
.trim();
|
||||
if (/[가-힣][a-z]{2,}/.test(out)) {
|
||||
out = await repairKoreanGlitches(out, lmUrl, model);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 한글+영문이 한 단어로 깨진 표기(LLM 토큰 꼬임)를 LLM 1회 호출로 교정.
|
||||
*/
|
||||
async function repairKoreanGlitches(text: string, lmUrl: string, model: string): Promise<string> {
|
||||
try {
|
||||
const res = await bridgeFetch<any>(BRIDGE_API.lm.proxy, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
url: `${lmUrl}/v1/chat/completions`,
|
||||
payload: {
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: '당신은 한국어 교정기입니다. 입력 텍스트에서 한글과 영문 알파벳이 한 단어로 잘못 합쳐진 깨진 표기(예: "핵ess"→"핵심", "결ently"→"결국")만 자연스러운 한국어로 교정합니다. 그 외 내용·문장·마크다운 구조·표·정상적인 영문 용어(API, JSON 등)는 한 글자도 바꾸지 않으며, 교정된 전체 텍스트만 그대로 출력합니다(설명·주석 금지).' },
|
||||
{ role: 'user', content: text },
|
||||
],
|
||||
temperature: 0,
|
||||
top_p: 0.7,
|
||||
top_k: 20,
|
||||
},
|
||||
}),
|
||||
}, { timeoutMs: 120_000 });
|
||||
const fixed = String(
|
||||
res?.choices?.[0]?.message?.content
|
||||
?? res?.choices?.[0]?.text
|
||||
?? res?.answer ?? res?.response ?? '',
|
||||
).replace(/\n*(?:```[\w]*\s*)?\[Self-Reflector Check\][\s\S]*$/i, '').trim();
|
||||
if (!fixed || fixed.length < text.length * 0.7) return text;
|
||||
return fixed;
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Datacollect bridge 가 자주 뱉는 환경 의존성 에러(Python 패키지 미설치, Python
|
||||
* 자체 부재 등) 를 패턴 매칭해서 사용자에게 *해결 명령까지* 알려주는 가이드 텍스트.
|
||||
* 없으면 빈 문자열 반환. slashRouter 의 catch 블록에서 일반 에러 메시지 뒤에 append.
|
||||
*/
|
||||
export function bridgeErrorRemedy(rawMsg: string): string {
|
||||
const msg = String(rawMsg || '');
|
||||
const pkgMatch = msg.match(/필수 패키지가 없습니다?[:\s]+([\w\-,\s.]+)/i)
|
||||
|| msg.match(/missing (?:python )?packages?[:\s]+([\w\-,\s.]+)/i);
|
||||
if (pkgMatch) {
|
||||
const pkgs = pkgMatch[1].split(/[,\s]+/).map((s) => s.trim()).filter(Boolean).join(' ');
|
||||
return `\n\n💡 **해결**: Datacollect bridge 가 도는 환경에서 아래 명령으로 누락된 Python 패키지를 설치하세요.\n\n`
|
||||
+ '```bash\n'
|
||||
+ `# macOS (homebrew Python — PEP 668 보호 우회):\n`
|
||||
+ `python3 -m pip install --user --break-system-packages ${pkgs}\n\n`
|
||||
+ `# 또는 가상환경(venv) 사용 시 그 venv 활성화 후:\n`
|
||||
+ `pip install ${pkgs}\n`
|
||||
+ '```\n\n'
|
||||
+ `설치 후 **bridge 재시작은 보통 불필요** — bridge 는 Python 을 child process 로 spawn 하므로 다음 호출이 바로 새 패키지를 인식합니다. 그래도 안 되면 \`npm run bridge\` 재시작.\n`;
|
||||
}
|
||||
if (/Python 3이 설치돼 있지 않거나 PATH/i.test(msg) || /command not found.*python/i.test(msg)) {
|
||||
return `\n\n💡 **해결**: Python 3 이 설치돼 있어야 합니다. https://www.python.org 에서 설치 후 터미널에서 \`python3 --version\` 으로 확인하세요. 이미 설치돼 있으면 PATH 설정 확인 필요.`;
|
||||
}
|
||||
if (/ECONNREFUSED|fetch failed/i.test(msg) || /연결할 수 없습니다/i.test(msg)) {
|
||||
return `\n\n💡 **해결**: Datacollect bridge 가 떠 있지 않습니다. \`Datacollector_MAC\` 프로젝트에서 \`npm run bridge\` 실행 후 다시 시도하세요.`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
Reference in New Issue
Block a user