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
+40 -64
View File
@@ -63,28 +63,16 @@ export interface TurnContextSink {
lessons: string[];
knowledgeMix: ResolvedKnowledgeMix | null;
/**
* [CONFLICT WARNINGS] 시스템 프롬프트 블록 — 빈 문자열이면 충돌 없음.
* buildAstraModeSystemPrompt 가 직접 prompt 에 주입.
* 동적 시스템 프롬프트 블록 — id → 본문. memoryContext 가 이 turn 에 채우고,
* buildAstraModeSystemPrompt 가 iterate 해서 [CONTEXT] 밖에 join 주입.
* Casual conversation 모드면 자동 skip. 빈 본문은 자동 제외.
*
* 등록 순서대로 prompt 에 join — `intent-clarification` → `terminology` →
* `conflict-warnings` → `cove-checklist` → `citation-trace`.
*/
conflictWarnings: string;
/**
* [VERIFICATION CHECKLIST] Chain-of-Verification 블록 — 빈 문자열이면 CoVe 비활성/근거 없음.
* 모델이 답변 *작성 전* 그라운딩 체크하도록 instructional prompt 주입.
*/
coveChecklist: string;
/**
* [INTENT CLARIFICATION GUIDANCE] 블록 — 질의가 모호한 차원이 감지된 경우 LLM 에게
* 답변보다 *역질문* 우선 지시. 빈 문자열이면 모호 차원 없음 또는 disable.
*/
intentClarification: string;
/**
* [CITATION TRACE] 블록 — 답변 끝에 사용 출처 한 줄 정리 지시. 검색 결과 있을 때만.
*/
citationTrace: string;
dynamicBlocks: Map<string, string>;
/** Post-hoc Self-Check 용 — selected chunks 의 (title, excerpt) 요약. */
selfCheckSources: Array<{ title: string; excerpt: string }>;
/** [TERMINOLOGY DICTIONARY] 시스템 프롬프트 블록 — 글로서리 파일 있을 때만 채워짐. */
terminology: string;
}
export interface MemoryContextDeps {
@@ -284,47 +272,46 @@ export async function buildMemoryContext(deps: MemoryContextDeps): Promise<strin
deps.turnCtx.lessons = lessonChunks.map((c) => c.content);
// Conflict Surface — selectedChunks 의 per-doc conflictSeverity 신호 + 교차-문서
// 발산 후보를 LLM 에 노출. 블록은 [CONTEXT] *밖*에 주입돼 token-truncation 보호.
// 설정으로 disable 가능 — 기본 켜져 있음 (v4 정책이 이미 CONFLICT WARNING 플래그 참조).
if (config.conflictHighlightingEnabled !== false) {
const threshold: ConflictThresholdSetting = (config.conflictSeverityThreshold || 'medium') as ConflictThresholdSetting;
deps.turnCtx.conflictWarnings = buildConflictWarningsBlock(result.selectedChunks, {
selfFlagThreshold: threshold,
crossDivergenceEnabled: config.conflictCrossDocEnabled !== false,
});
} else {
deps.turnCtx.conflictWarnings = '';
}
// 동적 시스템 프롬프트 블록 빌드 — 등록 순서대로 turnCtx.dynamicBlocks 에 set.
// 옛 named field 5개 (conflictWarnings/coveChecklist/intentClarification/citationTrace/
// terminology) 통합. 새 블록 추가 = 여기서 setBlock 한 줄.
const blocks = deps.turnCtx.dynamicBlocks;
// CoVe (Chain-of-Verification) — 답변 *작성 전* 그라운딩 체크리스트를 시스템 프롬프트에
// 주입. 모델이 한 패스 안에서 self-verify. Conflict Surface 와 보완 관계 — 충돌
// 데이터를 *어떻게* verify 할지 지시.
if (config.coveEnabled !== false) {
deps.turnCtx.coveChecklist = buildCoveChecklistBlock(result.selectedChunks, deps.currentPrompt, {
topSourcesCount: config.coveTopSourcesCount ?? 5,
strictMode: config.coveStrictMode === true,
});
} else {
deps.turnCtx.coveChecklist = '';
}
// Intent Clarification — 모호 차원 감지 시 *역질문 우선* 지시. CoVe / Citation 과
// 동일 패턴: instructional system prompt block.
// Intent Clarification — 답변보다 *역질문 우선*. 모호 아닐 때 빈 문자열 → join 시 자동 제외.
if (config.intentClarificationEnabled !== false) {
const strict = (config.intentClarificationStrictness || 'medium') as IntentStrictness;
const ambig = detectAmbiguity(deps.currentPrompt, strict);
deps.turnCtx.intentClarification = buildIntentClarificationBlock(ambig);
} else {
deps.turnCtx.intentClarification = '';
blocks.set('intent-clarification', buildIntentClarificationBlock(ambig));
}
// Citation Trace — 답변 끝에 출처 한 줄 명시 지시. CoVe Strict 의 가벼운 형제.
// 검색 결과가 있을 때만 의미 있음.
// Terminology Dictionary — 사용자 편집 글로서리. 파일 없으면 빈 문자열.
if (config.glossaryEnabled !== false) {
blocks.set('terminology', buildTerminologyBlock({
relPath: config.glossaryPath || '.astra/glossary.md',
maxBodyLength: config.glossaryMaxBodyLength ?? 4000,
}));
}
// Conflict Surface — selectedChunks 의 per-doc conflictSeverity + 교차-문서 발산.
if (config.conflictHighlightingEnabled !== false) {
const threshold: ConflictThresholdSetting = (config.conflictSeverityThreshold || 'medium') as ConflictThresholdSetting;
blocks.set('conflict-warnings', buildConflictWarningsBlock(result.selectedChunks, {
selfFlagThreshold: threshold,
crossDivergenceEnabled: config.conflictCrossDocEnabled !== false,
}));
}
// CoVe — 답변 *작성 전* 그라운딩 체크리스트.
if (config.coveEnabled !== false) {
blocks.set('cove-checklist', buildCoveChecklistBlock(result.selectedChunks, deps.currentPrompt, {
topSourcesCount: config.coveTopSourcesCount ?? 5,
strictMode: config.coveStrictMode === true,
}));
}
// Citation Trace — 답변 끝 출처 한 줄.
if (config.citationTraceEnabled !== false && result.selectedChunks.length > 0) {
deps.turnCtx.citationTrace = buildCitationTraceBlock(result.selectedChunks);
} else {
deps.turnCtx.citationTrace = '';
blocks.set('citation-trace', buildCitationTraceBlock(result.selectedChunks));
}
// Self-Check 용 source 미리보기 — agent.ts 가 post-stream 에서 사용.
@@ -333,17 +320,6 @@ export async function buildMemoryContext(deps: MemoryContextDeps): Promise<strin
excerpt: (c.content || '').slice(0, 200),
}));
// Terminology Dictionary — 사용자 편집 글로서리 파일을 시스템 프롬프트 블록으로 주입.
// 파일 없으면 빈 문자열 (no-op). 캐시 + mtime 체크로 매 turn 디스크 read 최소화.
if (config.glossaryEnabled !== false) {
deps.turnCtx.terminology = buildTerminologyBlock({
relPath: config.glossaryPath || '.astra/glossary.md',
maxBodyLength: config.glossaryMaxBodyLength ?? 4000,
});
} else {
deps.turnCtx.terminology = '';
}
// Lessons 블록은 일반 RAG context 보다 앞 — context-overflow truncation 에서 먼저
// 살아남게.
const lessonBlock = buildLessonChecklistBlock(lessonChunks.map((c) => ({ title: c.title, content: c.content })));
+72
View File
@@ -0,0 +1,72 @@
/**
* mtime-keyed file cache utility — 파일을 *parse 결과* 까지 캐싱.
*
* 배경: terminologyBlock.ts 와 termValidator.ts 가 같은 글로서리 파일에 *별도*
* 캐시 2개를 보유. "함께 무효화" 가 사람이 손으로 보장하는 invariant — 한쪽만
* 잊고 invalidate 안 하면 stale read 위험.
*
* 이 유틸은:
* - 같은 파일을 캐싱하는 모든 consumer 가 같은 cache 인스턴스 사용
* - mtime 자동 체크 — 파일이 바뀌면 자동 재read
* - `invalidate(filePath)` 가 한 번 호출되면 그 파일의 *모든 parse 결과* 무효화
*
* 사용:
* const cache = createMtimeFileCache<MyParsed>('terminology', (raw) => parseMine(raw));
* const data = cache.read('/path/to/file.md'); // 캐시 hit or read+parse
* cache.invalidate('/path/to/file.md'); // 캐시 비움
*/
import * as fs from 'fs';
interface CacheEntry<T> {
mtimeMs: number;
parsed: T;
}
export interface MtimeFileCache<T> {
/** 파일 read + parse + 캐시. mtime 같으면 캐시 hit. 파일 없으면 null. */
read(filePath: string): T | null;
/** 특정 파일 캐시 무효화. */
invalidate(filePath: string): void;
/** 모든 캐시 비움. */
clear(): void;
/** 디버그용 — 현재 캐시 사이즈. */
size(): number;
}
/**
* 새 mtime-keyed 캐시 생성. `name` 은 디버그용 라벨 (consumer 식별).
* `parse` 는 raw 파일 본문 → 파싱 결과. throw 시 read() 가 null 반환.
*/
export function createMtimeFileCache<T>(
_name: string,
parse: (raw: string, filePath: string) => T,
): MtimeFileCache<T> {
const cache = new Map<string, CacheEntry<T>>();
return {
read(filePath: string): T | null {
try {
if (!fs.existsSync(filePath)) return null;
const stat = fs.statSync(filePath);
const cached = cache.get(filePath);
if (cached && cached.mtimeMs === stat.mtimeMs) return cached.parsed;
const raw = fs.readFileSync(filePath, 'utf-8');
const parsed = parse(raw, filePath);
cache.set(filePath, { mtimeMs: stat.mtimeMs, parsed });
return parsed;
} catch {
return null;
}
},
invalidate(filePath: string): void {
cache.delete(filePath);
},
clear(): void {
cache.clear();
},
size(): number {
return cache.size;
},
};
}