7bec20620a
아키텍처 감사 결과 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>
85 lines
3.6 KiB
TypeScript
85 lines
3.6 KiB
TypeScript
/**
|
|
* TeamOps handlers 공통 헬퍼 — 4인 팀 운영 슬래시 명령 클러스터가 공유하는 함수·상수.
|
|
*
|
|
* 이전 위치: `src/features/datacollect/slashRouter.ts` 내부 module-local 함수
|
|
* (v2.2.196 에서 분리). 핸들러 도메인별 분할 시 공통 항목만 여기에.
|
|
*
|
|
* 추후 datacollect / system 핸들러도 비슷한 공유 helpers 가 필요해지면
|
|
* `src/features/_shared/` 로 promote 고려.
|
|
*/
|
|
|
|
/** 한국식 KRW 숫자 포맷 — 만/억 단위 자동. 마이너스 부호 보존. */
|
|
export function fmtKrw(n: number): string {
|
|
const sign = n < 0 ? '-' : '';
|
|
const abs = Math.abs(n);
|
|
if (abs >= 100_000_000) return `${sign}${(abs / 100_000_000).toFixed(2)}억`;
|
|
if (abs >= 10_000) return `${sign}${(abs / 10_000).toFixed(0)}만`;
|
|
return `${sign}${abs.toLocaleString('ko-KR')}`;
|
|
}
|
|
|
|
/** 한국식 금액 토큰 파싱 — "5000만", "1.5억", "300000", "1,500,000", "10k", "5m" 모두 인식. */
|
|
export function parseAmount(token: string): number | null {
|
|
if (!token) return null;
|
|
const s = token.replace(/[,_]/g, '').trim();
|
|
const m = s.match(/^(-?[\d.]+)\s*(억|만|k|m|b)?$/i);
|
|
if (!m) return null;
|
|
const base = parseFloat(m[1]);
|
|
if (!Number.isFinite(base)) return null;
|
|
let mul = 1;
|
|
const unit = (m[2] || '').toLowerCase();
|
|
if (unit === '억') mul = 100_000_000;
|
|
else if (unit === '만') mul = 10_000;
|
|
else if (unit === 'k') mul = 1_000;
|
|
else if (unit === 'm') mul = 1_000_000;
|
|
else if (unit === 'b') mul = 1_000_000_000;
|
|
return base * mul;
|
|
}
|
|
|
|
/** D-day 계산 — ISO 날짜 (YYYY-MM-DD) 받아 오늘부터 며칠 후인지. 음수면 지난 일수. */
|
|
export function daysUntil(isoDate: string | undefined, now: Date = new Date()): number | null {
|
|
if (!isoDate) return null;
|
|
const t = Date.parse(isoDate);
|
|
if (!Number.isFinite(t)) return null;
|
|
return Math.ceil((t - now.getTime()) / (24 * 60 * 60 * 1000));
|
|
}
|
|
|
|
export interface ParsedTaskOwner { owner: string | undefined; displayTitle: string; }
|
|
|
|
/**
|
|
* Task 제목·notes 에서 owner 추출. 두 패턴 지원:
|
|
* 1. 제목 prefix `[멤버] 실제 제목` — /task, /meet 가 등록한 형식
|
|
* 2. notes 의 `담당: @이름` 또는 `담당: 이름` — 일부 외부 등록 호환
|
|
*/
|
|
export function parseTaskOwner(title: string, notes?: string): ParsedTaskOwner {
|
|
const titlePrefix = title.match(/^\[([^\]]+)\]\s*(.+)$/);
|
|
if (titlePrefix) return { owner: titlePrefix[1].trim(), displayTitle: titlePrefix[2].trim() };
|
|
const notesMatch = (notes || '').match(/담당:\s*(?:@)?([\S]+)/);
|
|
if (notesMatch) return { owner: notesMatch[1].trim(), displayTitle: title };
|
|
return { owner: undefined, displayTitle: title };
|
|
}
|
|
|
|
/** /hire 파이프라인 단계 정렬 가중치. inbox 1, hired 7, terminal 99. */
|
|
export const STAGE_ORDER: Record<string, number> = {
|
|
inbox: 1, screened: 2, interview: 3, final: 4, offer: 5,
|
|
accepted: 6, hired: 7, rejected: 99, declined: 99,
|
|
};
|
|
|
|
/** 종료된 후보 단계. 검색·통계에서 active 와 구분. */
|
|
export const TERMINAL_STAGES = new Set(['hired', 'rejected', 'declined']);
|
|
|
|
/** 단계별 emoji — UI 표시. */
|
|
export function stageEmoji(stage: string): string {
|
|
switch (stage) {
|
|
case 'inbox': return '📥';
|
|
case 'screened': return '🔍';
|
|
case 'interview': return '💬';
|
|
case 'final': return '🎯';
|
|
case 'offer': return '📨';
|
|
case 'accepted': return '🤝';
|
|
case 'hired': return '🎉';
|
|
case 'rejected': return '❌';
|
|
case 'declined': return '🚪';
|
|
default: return '•';
|
|
}
|
|
}
|