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:
2026-06-01 11:55:22 +09:00
parent 15a34e0889
commit 7bec20620a
40 changed files with 4784 additions and 4545 deletions
+24 -106
View File
@@ -164,8 +164,7 @@ import { buildAgentModeSystemPrompt } from './agent/handlePrompt/buildAgentModeS
import { buildAstraModeSystemPrompt } from './agent/handlePrompt/buildAstraModeSystemPrompt';
import { computeBudgetedRequest } from './agent/handlePrompt/computeBudgetedRequest';
import { processFinalAnswer } from './agent/handlePrompt/processFinalAnswer';
import { postHocSelfCheck, formatSelfCheckFooter, DEFAULT_SELF_CHECK_OPTIONS } from './agent/postHocSelfCheck';
import { validateTermUsage, formatTermValidatorFooter } from './agent/termValidator';
import { runPostAnswerHooks } from './agent/postAnswerHooks';
import { applyAutoContinuation } from './agent/handlePrompt/applyAutoContinuation';
export interface ChatMessage {
@@ -287,28 +286,26 @@ export class AgentExecutor {
lessons: string[];
/** 이번 turn 에 결정된 Knowledge Mix — scope footer 표시용. */
knowledgeMix: ResolvedKnowledgeMix | null;
/** [CONFLICT WARNINGS] 시스템 프롬프트 블록 — 검색된 출처에서 충돌 신호 감지 시. */
conflictWarnings: string;
/** [VERIFICATION CHECKLIST] Chain-of-Verification 블록 — 그라운딩 자기 검증 지시. */
coveChecklist: string;
/** [INTENT CLARIFICATION GUIDANCE] — 모호 질의 감지 시 역질문 우선 지시. */
intentClarification: string;
/** [CITATION TRACE] — 답변 끝에 사용 출처 한 줄 정리 지시. */
citationTrace: string;
/**
* 동적 시스템 프롬프트 블록 레지스트리 — turn 마다 memoryContext 가 채우고
* buildAstraModeSystemPrompt 가 iterate 해서 prompt 에 주입.
*
* 옛 구조: conflictWarnings/coveChecklist/intentClarification/citationTrace/terminology
* 5개 named field + 5개 reset + 5개 named param + 5개 ternary gate (총 25곳 edit).
* 새 구조: 1 Map. 새 블록 추가 = 1 set call.
*
* Key 는 디버그·재정의용 id (예: 'conflict-warnings'). Value 는 이미 빌드된
* 블록 본문 — 빈 문자열이면 주입 안 함. casual mode 게이팅은 호출자가 처리.
*/
dynamicBlocks: Map<string, string>;
/** Self-check 용 — selected chunks 의 (title, content) 요약. memoryContext 가 채움. */
selfCheckSources: Array<{ title: string; excerpt: string }>;
/** [TERMINOLOGY DICTIONARY] — 사용자 편집 글로서리 + Term Check 지침. */
terminology: string;
} = {
retrieval: null,
lessons: [],
knowledgeMix: null,
conflictWarnings: '',
coveChecklist: '',
intentClarification: '',
citationTrace: '',
dynamicBlocks: new Map(),
selfCheckSources: [],
terminology: '',
};
/** Per-turn state 일괄 정리. turn 시작/abort/load session 시 호출. */
@@ -316,12 +313,8 @@ export class AgentExecutor {
this._turnCtx.retrieval = null;
this._turnCtx.lessons = [];
this._turnCtx.knowledgeMix = null;
this._turnCtx.conflictWarnings = '';
this._turnCtx.coveChecklist = '';
this._turnCtx.intentClarification = '';
this._turnCtx.citationTrace = '';
this._turnCtx.dynamicBlocks.clear();
this._turnCtx.selfCheckSources = [];
this._turnCtx.terminology = '';
}
private readonly options: AgentExecutorOptions;
@@ -673,11 +666,7 @@ export class AgentExecutor {
isCasualConversation,
localPathContext,
knowledgeMix: this._turnCtx.knowledgeMix,
conflictWarningsCtx: this._turnCtx.conflictWarnings,
coveChecklistCtx: this._turnCtx.coveChecklist,
intentClarificationCtx: this._turnCtx.intentClarification,
citationTraceCtx: this._turnCtx.citationTrace,
terminologyCtx: this._turnCtx.terminology,
dynamicBlocks: this._turnCtx.dynamicBlocks,
});
// Context budget computation → src/agent/handlePrompt/computeBudgetedRequest.ts
const imageCount = (reqMessages as any[])
@@ -1221,25 +1210,21 @@ export class AgentExecutor {
memoryLayers: this._turnCtx.retrieval?.usedMemoryLayers ?? [],
note: `continuations=${continuationCount} historyDropped=${reqMessages.length - budgetedHistory.length}`,
});
// ── Devil Agent (도현) — 비활성 시 silent skip. 활성 시 별도 LLM 호출로 반박 카드 emit. ──
// 비동기 — main turn 완료에 영향 없음. 실패해도 main 답변은 보존됨.
void this._maybeEmitDevilRebuttal({
// ── Post-answer hooks (v2.2.197) — Devil + SelfCheck + TermValidator 통합 레지스트리. ──
// 새 hook 추가 = `src/agent/postAnswerHooks/index.ts` 에 한 객체 push.
// 안전 fallback 내장 — 한 hook 실패가 다른 hook / main turn 영향 없음.
runPostAnswerHooks({
userPrompt: prompt || '',
assistantAnswer: finalAssistantContent,
baseUrl: ollamaUrl,
modelName: actualModel,
contextLength: ctxLimits.contextLength,
engine,
selfCheckSources: this._turnCtx.selfCheckSources,
callNonStreaming: (p) => this.callNonStreaming(p),
getAbortSignal: () => this.abortController?.signal,
getWebview: () => this.webview,
});
// ── Post-hoc Self-Check (v2.2.191) — 별도 LLM 호출 1회로 답변 사후 검증. ──
// 비동기 — Devil 과 동일 패턴. 결과를 footer 한 줄로 append.
void this._maybePostHocSelfCheck({
userPrompt: prompt || '',
assistantAnswer: finalAssistantContent,
});
// ── Term Validator (v2.2.194) — 결정론적 정규식 스캔. LLM 호출 없음, 즉시 실행. ──
// 글로서리 forbidden 단어가 답변에 등장 시 footer flag. 위반 없으면 ✓.
this._maybeRunTermValidator(finalAssistantContent);
} else {
this.webview.postMessage({ type: 'streamChunk', value: finalAssistantContent });
}
@@ -1394,73 +1379,6 @@ export class AgentExecutor {
* "lock() request could not be registered" error this method is helping
* to avoid.
*/
/**
* Devil Agent 반박 emit — main turn 완료 직후 호출 (fire-and-forget).
* 비활성 시 즉시 return. 활성 시 별도 LLM 호출 (callNonStreaming 재사용) 로 짧은 비판 생성.
* 성공 시 webview 에 'devilRebuttal' 메시지 전송 → UI 가 카드로 렌더.
*/
private async _maybeEmitDevilRebuttal(opts: {
userPrompt: string;
assistantAnswer: string;
baseUrl: string;
modelName: string;
contextLength: number;
engine: 'lmstudio' | 'ollama';
}): Promise<void> {
return maybeEmitDevilRebuttalFn({
getAbortSignal: () => this.abortController?.signal,
callNonStreaming: (p) => this.callNonStreaming(p),
getWebview: () => this.webview,
}, opts);
}
/**
* Post-hoc Self-Check — 답변 *완료 후* LLM 1회 호출로 3가지 평가
* (사용자 질의 직접 답함 / 출처 그라운딩 / 논리 모순). 비동기 — main turn 에 영향 없음.
* 기본 OFF (g1nation.selfCheckEnabled). 결과는 footer 한 줄로 streamChunk append.
*/
private async _maybePostHocSelfCheck(opts: {
userPrompt: string;
assistantAnswer: string;
}): Promise<void> {
try {
const cfg = getConfig();
if (!cfg.selfCheckEnabled) return;
if (!opts.userPrompt.trim() || !opts.assistantAnswer.trim()) return;
const model = (cfg.selfCheckModel || '').trim() || cfg.defaultModel;
if (!model || !cfg.ollamaUrl) return;
const sources = this._turnCtx.selfCheckSources || [];
const result = await postHocSelfCheck(opts.userPrompt, opts.assistantAnswer, sources, {
ollamaUrl: cfg.ollamaUrl,
model,
timeoutMs: (cfg.selfCheckTimeoutSec ?? 6) * 1000,
excerptLength: DEFAULT_SELF_CHECK_OPTIONS.excerptLength,
maxSources: DEFAULT_SELF_CHECK_OPTIONS.maxSources,
});
// 성공 실패 모두 footer 표시 — 사용자가 self-check 가 *돌고 있는지* 알 수 있게.
// 실패 시 흐릿한 한 줄, 성공 시 평가 한 줄.
const footer = formatSelfCheckFooter(result, model);
this.webview?.postMessage({ type: 'streamChunk', value: footer });
} catch { /* swallow — self-check never breaks the turn */ }
}
/**
* Post-gen Term Validator — 글로서리 forbidden 단어가 답변에 등장하는지 결정론적 스캔.
* LLM 호출 없음, 동기 실행 (수 ms). 글로서리 없거나 disabled 면 silent no-op.
*/
private _maybeRunTermValidator(answer: string): void {
try {
const cfg = getConfig();
if (cfg.termValidatorEnabled === false) return;
if (!answer || !answer.trim()) return;
const result = validateTermUsage(answer, cfg.glossaryPath || '.astra/glossary.md');
if (!result.ran || result.dictionarySize === 0) return; // 글로서리 없거나 비어 있음 → 표시 안 함
const footer = formatTermValidatorFooter(result);
if (footer) this.webview?.postMessage({ type: 'streamChunk', value: footer });
} catch { /* swallow — validator never breaks the turn */ }
}
private async callNonStreaming(params: {
baseUrl: string;