/** * Forgen v0.5.0 — Injection ROI 루프 (ADR-010 W3-1, F2) * * 근거: v0.4.11 실측에서 forgen 의 δ 는 100% injection 에서 나왔다 (blocks=0). * 따라서 injection 품질 = 효과 그 자체다. "surfaced 는 많은데 acted_on 이 * 없는" 솔루션은 컨텍스트 비용만 내는 저 ROI 주입이므로 자동 강등한다. * native 메모리(claude-mem 등)에는 없는 acted-on 피드백 루프 — moat 기능. * * 설계 (Rev 2 — 리뷰에서 기존 solution-quarantine 재사용 불가 확정): * - 전용 저장소 `~/.forgen/state/roi-demotions.json` * (기존 quarantine 은 frontmatter 파스 에러 기반 — 시맨틱 불일치) * - `ranking-pipeline.ts` 는 순수 유지 — 강등은 matchSolutions 결과 후처리 * - 판정 갱신은 auto-compound 세션 종료 시 (updateRoiDemotions) * - 임계 (config.json roiDemotion 으로 조정 가능): * surfaced_90d >= 3 && acted/surfaced < 0.1 → 강등 (relevance ×0.5) * 2회 연속 강등 유지 → 격리 (주입 제외) * acted_on 신규 발생 → 즉시 해제 */ import { type HitRateRow } from '../core/observability-store.js'; export interface RoiDemotionEntry { solutionId: string; reason: 'low-roi'; demotedAt: string; /** 연속 강등 유지 윈도 수 — 2 이상이면 격리(주입 제외) */ windowCount: number; /** 마지막 windowCount 증가 시점 — 24h 미만 재평가는 증가 없음 (리뷰 SEV-2: * 평가는 auto-compound 세션마다 도는데, 같은 날 2세션으로 격리되면 * "2회 연속 윈도" 설계 의도 위반. 90d 통계는 당일 내 사실상 불변이라 * 같은 날 재평가는 새 정보가 없다.) */ lastEvaluatedAt: string; surfaced: number; actedOn: number; } export type RoiDemotions = Record; export interface RoiThresholds { /** 판정에 필요한 최소 노출 수 (90d). 소규모 사용자에서 dead-code 방지 위해 3. */ surfacedMin: number; /** 이 미만이면 저 ROI (acted/surfaced) */ rateMax: number; } export declare const DEFAULT_ROI_THRESHOLDS: RoiThresholds; export declare function roiDemotionsPath(home?: string): string; export declare function loadRoiDemotions(home?: string): RoiDemotions; export declare function saveRoiDemotions(demotions: RoiDemotions, home?: string): void; /** * 90d 윈도 기준 재판정. 반환값이 새 저장 상태다. * * - 신규 강등: surfaced ≥ min && rate < max → windowCount=1 * - 유지: 이미 강등 && 여전히 저 ROI → windowCount+1 (2부터 격리) * - 해제: acted_on 이 저장 시점보다 증가 (사용자가 실제로 씀) 또는 * rate 가 임계 이상으로 회복 → 엔트리 제거 * - 유예: surfaced < min 인 솔루션은 판정하지 않음 (신규/저노출 보호) */ export declare function evaluateRoiDemotions(rows: HitRateRow[], prev: RoiDemotions, thresholds?: RoiThresholds, now?: () => string): RoiDemotions; /** 격리 여부 — 2회 연속 윈도 강등 유지 시 주입에서 제외 */ export declare function isRoiQuarantined(entry: RoiDemotionEntry): boolean; export interface RoiAdjustable { name: string; relevance: number; } /** * 강등 ×0.5, 격리는 제외. relevance 변경 후 내림차순 재정렬. * fail-open: demotions 가 비면 원본 그대로. * * 실효 (리뷰에서 명시화): injector 의 MIN_INJECT_RELEVANCE 가 0.3 이므로 * relevance < 0.6 인 강등 솔루션은 사실상 주입 차단된다 — 강등은 * "중간 신뢰도엔 soft-block, 고신뢰도(≥0.6)엔 우선순위 하락"으로 동작하고, * 격리는 relevance 무관 hard-block. 이 기울기는 의도된 것: δ 가 injection * 품질에서 나오므로 저 ROI 주입엔 보수적으로 군다. */ export declare function applyRoiDemotions(matches: T[], demotions: RoiDemotions): T[]; /** * observability 이벤트 → 판정 → 저장. fail-open. * @returns 강등/격리 수 (로그용) 또는 null (판정 불가) */ export declare function updateRoiDemotions(home?: string): { demoted: number; quarantined: number; } | null;