1
0
Fork 0
Everything2Everything/docs/ssot/_data/analyses.json
Yun Chan 232f453e15 docs: 변환 그래프 OS 마스터플랜 SSOT (멀티에이전트 분석+리서치 종합)
전체 소스 심층 분석(5) + 인터넷 리서치(7) → 3 독립 아키텍트 → 심사 랭킹 → 마스터플랜 종합을 Workflow로 오케스트레이션해 생성.

- docs/ssot/_data/*.json : SSOT 원천 데이터 (master/analyses/researches/designs/ranking)
- docs/ssot/build.py : JSON → index.html(웹 대시보드) + PLAN.md 제너레이터
- 9 ADR · 8단계 로드맵(P1 그래프엔진+PDF압축 → P8 굳히기)
- 양방향/다방향 그래프 라우팅, Codex OAuth+API, FFmpeg/Ghostscript 미디어 레이어
2026-06-01 11:38:36 +09:00

576 lines
No EOL
70 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{
"key": "core-engine",
"subsystem": "Core 추상화 & 변환 엔진 (Everything2Everything.Core)",
"summary": "변환 능력을 IConverterProvider로 추상화하고, ProviderRegistry가 (입력확장자, 출력확장자) 쌍을 단일 홉 딕셔너리로 매핑하며, ConversionEngine이 단일/배치/결합 변환을 오케스트레이션하는 구조다. 양방향 N×M 매트릭스는 ProviderCapability.PairsFromMatrix로 각 Provider가 자기 입력·출력의 데카르트 곱을 선언해 표현하지만, 멀티홉 경로는 엔진이 아니라 각 Provider 내부에 하드코딩(DocxProvider/HwpxProvider→PdfProvider, HeicProvider→MagickProvider, DocumentProvider의 거대 switch)되어 있다. 이미지 중심으로 설계가 견고하게 동작하지만, 진정한 다방향 변환·AI·영상/오디오로 확장하려면 핵심 추상화 자체의 재설계가 필요하다.",
"strengths": [
"ConversionPair.Normalize(ConversionPair.cs:8)로 확장자 정규화를 한 곳에 모으고, 모든 룩업·비교를 OrdinalIgnoreCase로 일관되게 처리해 대소문자/점 누락 버그를 구조적으로 차단함",
"ProviderCapability(ProviderCapability.cs:18)가 능력 선언(SupportedConversions)·상태(ProviderStatus)·외부 의존성(ExternalDependency)·로드맵을 하나의 불변 record로 캡슐화 — UI가 메뉴/가용성을 메타데이터만으로 생성할 수 있는 데이터 주도 설계",
"CheckAvailabilityAsync/ProviderAvailability(IConverterProvider.cs:7,18) 패턴으로 외부 도구(LibreOffice·WebView2·Windows OCR) 부재를 변환 실행 전에 친절한 사유·다운로드 URL과 함께 보고함",
"ConvertResult(ConvertResult.cs:10)의 Ok/Fail/Skip 팩토리와 ConvertStatus 3분류로 부분 실패/건너뜀을 일관되게 표현하고, 예외를 결과 객체로 변환(ConversionEngine.cs:121)해 배치 중 한 파일 실패가 전체를 중단시키지 않음",
"IProgress<double> 진행률을 멀티홉 구간별로 분할 합성(DocxProvider.cs:103 `0.55+p*0.45`, HeicProvider.cs:61)해 체인 변환에서도 매끄러운 진행 표시를 제공함",
"CancellationToken이 엔진→Provider→외부 프로세스(proc.Kill)까지 일관되게 전파되어 취소가 실제로 동작함"
],
"weaknesses": [
"멀티홉 경로 탐색의 부재가 가장 큰 부채: ProviderRegistry는 단일 (input,output) 룩업만 하고(ProviderRegistry.cs:43,49) 그래프가 없어, A→B→C 같은 경로는 매번 Provider 내부에 손으로 짜야 한다. DocumentProvider.RouteAsync(DocumentProvider.cs:92-205)는 사실상 사람이 손으로 그린 경로 그래프이며, 형식이 늘어날수록 switch가 조합 폭발한다",
"Provider 간 체이닝이 생성자 주입으로 하드와이어됨: Bootstrap이 new HeicProvider(magick), new DocxProvider(pdf), new OcrProvider(pdf)처럼 의존성을 수동 결선(Everything2EverythingBootstrap.cs:9-21)한다. 새 중간 형식(예: DOCX→PDF→이미지 외에 DOCX→HTML→이미지)을 자동으로 발견할 방법이 없다",
"ProviderRegistry._byPair.TryAdd(ProviderRegistry.cs:22)는 같은 (input,output)을 여러 Provider가 선언하면 '먼저 등록된 것이 이긴다'를 조용히 적용한다. 우선순위/품질 기반 선택이 불가능하고, 충돌이 경고 없이 묻힌다(예: docx→png을 DocxProvider와 잠재적 다른 Provider가 동시 주장 시)",
"ConversionEngine이 ImageMagick에 직접 의존(ConversionEngine.cs:2)하고 CombineAsync/LoadImageForCombine/ApplyCombineEncoding(164-257)에서 MagickImageCollection을 직접 조작 — '결합'이 Provider 추상화 밖에 있어 엔진이 특정 라이브러리에 결합(coupling)되고, 이미지 외 결합(PDF 병합·동영상 concat)으로 확장 불가",
"ConvertOptions(ConvertOptions.cs:17-58)가 11개 sub-record를 가진 갓 오브젝트로, 형식 추가마다 sub-record가 늘어난다. 모든 Provider가 동일한 거대 옵션을 받지만 대부분 무시하고, 영상/오디오/코덱/AI 관련 옵션을 담을 자리가 없다",
"IConverterProvider(IConverterProvider.cs:9-15)가 '단일 파일 경로 in → outputDirectory에 파일 out, IProgress<double>'로 고정되어 있어 스트리밍, 다중 입력(N→1 결합), AI 프롬프트/모델 파라미터, 미디어 메타데이터(코덱·비트레이트·길이) 프로빙을 표현할 수 없다",
"Provider 목록이 컴파일타임 고정(Bootstrap의 배열). 플러그인 DLL 동적 로딩, ProviderStatus.ComingSoon을 실제 구현으로 교체할 확장 지점이 없다",
"결합 가능 형식이 ConversionEngine의 정적 HashSet(CombinableInputs/Outputs, ConversionEngine.cs:14-23)에 박혀 있어 Provider의 능력 선언과 이중 관리되고 동기화가 깨지기 쉽다"
],
"extensibilityBlockers": [
"ProviderRegistry.cs:6,43,49 — 매칭이 Dictionary<(Input,Output)> 단일 홉뿐. 멀티홉 경로 탐색(BFS/Dijkstra) API가 전혀 없어 양방향·다방향 극대화의 근본 한계",
"ProviderRegistry.cs:22 — `_byPair.TryAdd`가 동일 변환쌍 충돌을 조용히 첫 등록자 우선으로 삼킴. 비용/품질 가중치 기반 경로 선택 불가",
"ConversionEngine.cs:2 + 164-257 — 엔진이 ImageMagick(MagickImageCollection)에 직접 의존. 결합 로직이 Provider 밖에 있어 추상화 누수, 비이미지 결합 확장 차단",
"IConverterProvider.cs:9-15 — 시그니처가 (sourcePath, outputDirectory, outputExtension, ConvertOptions, IProgress<double>) 단일 파일·단일 출력 디렉터리·실수 진행률로 고정. 다중 입력·스트리밍·AI 파라미터·미디어 프로빙 표현 불가",
"Everything2EverythingBootstrap.cs:9-21 — Provider 인스턴스와 체이닝 의존성이 컴파일타임 하드코딩. 동적 플러그인 로딩/등록 지점 부재",
"ConvertOptions.cs:35-55 — 11개 이미지/문서 중심 sub-record. 영상 코덱/비트레이트/프레임레이트, 오디오, AI(모델·프롬프트·온도) 옵션을 담을 구조가 없고 형식마다 sub-record 증식",
"DocumentProvider.cs:92-205 — 멀티홉 라우팅이 손으로 짠 switch 그래프. 형식 N개에 대해 경로가 O(N^2)로 수동 증식, 새 형식 추가 시 모든 분기 갱신 필요",
"ConvertResult.cs:10 — 결과가 출력 파일 경로 리스트만 담음. 추출 텍스트·AI 응답·메타데이터·중간 산출물 같은 비파일 결과를 표현할 필드 없음"
],
"improvementOpportunities": [
{
"title": "ProviderRegistry를 변환 그래프로 승격하고 엔진에 멀티홉 경로 탐색(BFS/Dijkstra) 추가",
"rationale": "확장자를 노드, ConversionPair를 간선, Provider 비용/품질을 가중치로 하는 유향 그래프를 구성하면 DocxProvider→PdfProvider, DocumentProvider.RouteAsync 같은 손코딩 체인을 엔진이 자동 합성한다. 임시 파일 체이닝을 표준화하면 '진짜 다방향 변환 극대화'가 코드 추가 없이 새 형식 조합으로 폭발적으로 늘어난다 — 프로젝트 핵심 목표의 직접 달성",
"impact": "high",
"effort": "high"
},
{
"title": "CombineAsync를 IMultiInputProvider(또는 N→1 Provider 추상화)로 분리해 엔진의 ImageMagick 직접 의존 제거",
"rationale": "ConversionEngine.cs:164-257의 ImageMagick 결합 로직을 ImageCombineProvider로 옮기면 엔진이 라이브러리 중립이 되고, PDF 병합·동영상 concat·오디오 믹스 같은 다른 N→1 결합을 동일 추상화로 추가할 수 있다. CombinableInputs/Outputs 정적 HashSet과 Provider 능력 선언의 이중 관리도 해소",
"impact": "high",
"effort": "medium"
},
{
"title": "IConverterProvider 시그니처를 ConvertRequest/ConvertContext 객체로 일반화하고 다중 입력·비파일 결과·미디어 메타데이터 지원",
"rationale": "현재 (sourcePath,outputDir,ext,options,progress) 고정 시그니처는 N→1 결합, 스트리밍, OCR 텍스트/AI 응답 같은 비파일 산출물, 영상 프로빙을 표현 못 한다. 요청/컨텍스트 객체로 감싸면 인터페이스 파괴 없이 영상·오디오·AI 변환을 같은 추상화로 흡수 가능",
"impact": "high",
"effort": "high"
},
{
"title": "AI/LLM 변환을 위한 IAiConverterProvider 확장과 ConvertOptions.Ai 옵션 도입",
"rationale": "Codex/LLM 통합(요약·번역·이미지 캡션·문서 재구성)은 모델 ID·프롬프트·온도·API 키 같은 파라미터가 필요한데 현재 ConvertOptions와 IConverterProvider에는 자리가 없다. OcrProvider가 Windows OCR을 Provider로 깔끔히 흡수한 선례가 있으므로 AI도 Provider로 표현하면 변환 매트릭스에 자연스럽게 편입된다",
"impact": "high",
"effort": "medium"
},
{
"title": "Provider 동적 로딩(플러그인) 도입 — Bootstrap 하드코딩 제거",
"rationale": "Everything2EverythingBootstrap.cs:9-21의 컴파일타임 배열을 어셈블리 스캔/MEF/DI 기반 등록으로 바꾸면 영상(FFmpeg)·AI·신규 코덱 Provider를 본체 재컴파일 없이 추가할 수 있다. ProviderStatus.ComingSoon 항목을 별도 플러그인으로 점진 구현하는 길도 열림",
"impact": "medium",
"effort": "high"
},
{
"title": "ProviderRegistry 변환쌍 충돌을 명시적 우선순위/진단으로 전환",
"rationale": "_byPair.TryAdd(ProviderRegistry.cs:22)의 '첫 등록자 승리'는 조용한 데이터 손실이다. Provider에 우선순위/품질 점수를 부여하고 충돌 시 로그·진단을 남기면, 같은 변환을 더 빠르거나 고품질로 하는 Provider를 의도적으로 선택할 수 있어 경로 비용 가중치(첫 개선안)와도 연결된다",
"impact": "medium",
"effort": "low"
},
{
"title": "ConvertOptions를 형식별 옵션 백(IReadOnlyDictionary 또는 옵션 프로바이더)으로 분해",
"rationale": "11개 sub-record 갓 오브젝트(ConvertOptions.cs)는 형식 추가마다 비대해지고 모든 Provider가 무관한 옵션을 끌고 다닌다. Provider가 자기 옵션 스키마를 선언하고 엔진이 형식별 옵션만 주입하면 영상/오디오/AI 옵션을 코어 record 증식 없이 수용",
"impact": "medium",
"effort": "medium"
}
],
"keyFiles": [
{
"path": "src/Everything2Everything.Core/Providers/IConverterProvider.cs",
"role": "변환 능력의 핵심 인터페이스. 단일 파일 in/out·IProgress<double>로 고정되어 영상/AI/스트리밍 확장의 1차 제약 지점"
},
{
"path": "src/Everything2Everything.Core/Providers/ProviderRegistry.cs",
"role": "(input,output) 단일 홉 딕셔너리 매칭. 멀티홉 그래프 탐색 부재와 TryAdd 충돌 무시의 근원"
},
{
"path": "src/Everything2Everything.Core/ConversionEngine.cs",
"role": "변환 오케스트레이터. 단일/배치는 Provider에 위임하나 결합(CombineAsync)에서 ImageMagick에 직접 의존하는 추상화 누수"
},
{
"path": "src/Everything2Everything.Core/ConvertOptions.cs",
"role": "11개 sub-record 갓 오브젝트. 이미지/문서 중심이며 영상·오디오·AI 옵션 자리 부재"
},
{
"path": "src/Everything2Everything.Core/Converters/DocumentProvider.cs",
"role": "RouteAsync(92-205)가 손코딩 멀티홉 경로 그래프. 그래프 탐색 부재를 Provider 내부 switch로 메우는 패턴의 대표 사례"
},
{
"path": "src/Everything2Everything.Core/Converters/DocxProvider.cs",
"role": "PdfProvider를 생성자 주입받아 DOCX→PDF→이미지 체인을 수동 결선(104). Provider 간 하드와이어 체이닝의 전형"
},
{
"path": "src/Everything2Everything.Core/Everything2EverythingBootstrap.cs",
"role": "Provider 인스턴스·의존성을 컴파일타임 하드코딩. 동적 플러그인 로딩 부재의 진원지"
},
{
"path": "src/Everything2Everything.Core/Providers/ProviderCapability.cs",
"role": "능력·상태·외부의존성 메타데이터 캡슐화. PairsFromMatrix로 N×M 양방향 매트릭스를 선언적으로 표현하는 강점 지점"
}
]
},
{
"key": "providers",
"subsystem": "Provider 매트릭스 전체 (8개 IConverterProvider + ProviderRegistry 디스패치)",
"summary": "8개 Provider(Magick/Heic/Pdf/Docx/Html/Hwpx/Ocr/Document)가 `IConverterProvider`를 구현하고, 각자 `PairsFromMatrix(inputs, outputs)`로 N×M 변환 쌍을 카르테시안 곱으로 선언하면 `ProviderRegistry`가 `(input,output)→provider` 딕셔너리로 평탄화해 디스패치한다. 실제 변환은 대부분 \"중간 포맷으로 정규화 후 위임\"하는 파이프라인 — 이미지는 Magick로, 문서/한글은 LibreOffice→PDF→Magick로, HTML은 WebView2→PNG/PDF로 수렴한다. 결과적으로 매트릭스는 \"거의 모든 것 → 이미지/PDF/텍스트\" 방향으로만 풍부하고, 그 역방향(이미지/PDF → 편집가능 문서, HWP 출력, 미디어/아카이브 전 카테고리)이 구조적으로 비어 있다. 양방향·다방향·AI·미디어 코덱이라는 프로젝트 목표 대비 현재는 단방향 래스터라이저 집합에 가깝다.",
"strengths": [
"일관된 추상화: 모든 Provider가 `IConverterProvider`(Capability/CheckAvailability/ConvertAsync) 단일 인터페이스를 구현하고, `ProviderCapability.PairsFromMatrix`(ProviderCapability.cs:62-71)로 변환 쌍을 선언적으로 생성 → 신규 Provider 추가 시 보일러플레이트 최소.",
"레지스트리 기반 O(1) 디스패치: `ProviderRegistry`가 `(input,output)` 키 딕셔너리(_byPair, ProviderRegistry.cs:6,21-22)로 평탄화해 ConversionEngine이 입력 확장자만으로 즉시 Provider를 찾는다. 입력→가능출력 역인덱스(_outputsByInput)도 미리 구축해 UI 셀렉터/컨텍스트메뉴에 바로 공급.",
"중간 포맷 위임 패턴으로 코드 재사용: Heic→(PNG)→Magick, Docx/Hwpx→(PDF)→PdfProvider.ConvertCore, OCR→(PDF페이지 PNG)→Windows OCR. PdfProvider/MagickProvider가 공용 백엔드로 재활용되어 신규 입력 포맷이 이미지 출력 전체 매트릭스를 거의 공짜로 획득.",
"외부 의존성을 1급 모델로 표현: `ExternalDependency`(이름/설명/다운로드URL/필수여부)와 `ProviderAvailability.NotReady(reason, missing)`로 미설치 상태를 구조화해 사용자에게 안내 가능 — DocxProvider는 Word COM 우선/LibreOffice 폴백까지 이중화.",
"취소·실패·충돌 처리가 표준화: 모든 Provider가 CancellationToken을 전파하고 외부 프로세스는 `proc.Kill(true)`로 정리, `OutputPathHelper.ResolveOutputPath/ShouldSkip`으로 파일명 충돌(AppendNumber/Overwrite/Skip)을 통일 처리, 임시파일은 finally에서 삭제.",
"HTML 렌더링의 STA 디스패처 격리(HtmlProvider.cs:153-181): WebView2를 별도 STA 스레드+자체 Dispatcher 루프에서 구동해 메인 UI 스레드와 분리 — 배치 변환 중 UI 프리징/COM 아파트먼트 충돌 회피."
],
"weaknesses": [
"매트릭스가 사실상 단방향: 거의 모든 입력이 이미지/PDF/텍스트로 '나가기만' 한다. 편집가능 포맷으로 되돌아오는 경로가 OCR(이미지/PDF→txt/docx, 그것도 레이아웃 손실 평문)뿐이고, 진짜 구조 보존 역변환(PDF→DOCX, 이미지→벡터/문서)이 전무.",
"PDF가 입력으로만 풍부하고 출력이 빈약: PdfProvider는 PDF→이미지 렌더링만 한다(PdfProvider.cs:11-12). 이미지/HTML/DOCX→PDF는 각기 다른 Provider가 따로 만들지만, PDF→PDF(압축/병합/분할/회전)와 PDF→DOCX가 없어 프로젝트 핵심 목표인 'PDF 압축'을 어떤 Provider도 수행하지 못함.",
"HWP는 출력 불가가 구조적 한계로 고착: DocumentProvider가 HWP/HWPX를 입력으로만 받고 출력 Outputs 배열에 .hwp가 없다(DocumentProvider.cs:24, RoadmapNote:40). H2Orestart가 쓰기를 지원 안 해 'HWP↔DOCX/PDF/HTML' 목표의 절반(→HWP)이 막혀 있음.",
"DOC/DOCX 출력의 정의 충돌·왕복 불가능: DocxProvider는 .docx를 입력으로만(→PDF/이미지), DocumentProvider는 .docx를 입력이자 출력으로 선언하지만 OCR도 .docx를 출력한다. 'DOCX 편집본을 다시 받는' 일관된 단일 경로가 없고, DOCX→DOCX 같은 동일포맷은 ConversionEngine에서 Skip 처리됨.",
"외부 프로세스 안정성 취약점: LibreOffice 호출이 3곳(DocxProvider/HwpxProvider/DocumentProvider)에 복붙되어 있고 타임아웃이 전혀 없다 — soffice가 멈추면 WaitForExitAsync가 무한 대기(취소 토큰에만 의존). 또한 soffice는 동일 사용자 프로필을 공유해 동시 인스턴스 충돌 위험이 있으나 직렬화/락이 없음.",
"AI/LLM 통합 지점 부재: ConvertOptions에 OCR 백엔드 문자열('auto')만 있을 뿐, Codex/LLM 호출을 위한 추상화(요약/번역/생성형 변환/캡셔닝)가 인터페이스·옵션·Provider 어디에도 없음. 현재 구조에 AI를 끼워넣을 확장점이 설계되지 않음.",
"미디어·아카이브·폰트·CAD 카테고리 전무: 영상(mp4/mov/webm), 오디오(mp3/wav/flac), 아카이브(zip/7z/tar), 폰트(ttf/otf/woff), 벡터(svg/eps), CAD(dwg/dxf), 전자책(epub/mobi)을 다루는 Provider가 0개. `ProviderStatus.ComingSoon`은 enum에만 존재하고 이를 사용하는 Provider가 하나도 없어 로드맵 UI가 빈 상태.",
"레지스트리의 조용한 우선순위 함정: `_byPair.TryAdd`(ProviderRegistry.cs:22)는 첫 등록 Provider가 승리하고 후속은 '말없이 무시'된다. 향후 같은 (input,output) 쌍을 두 Provider가 선언하면(예: PDF 압축 vs PDF 렌더) 부트스트랩 순서에 따라 비결정적으로 한쪽이 사라짐 — 경고도 없음."
],
"extensibilityBlockers": [
"IConverterProvider.cs:9-15 — ConvertAsync 시그니처가 단일 sourcePath/단일 outputExtension에 고정. 다방향(N입력→1출력 병합, 1입력→N출력 동시) 변환과 '입력=출력 동일포맷 최적화(PDF압축, 이미지 리인코딩)'가 인터페이스 레벨에서 표현 불가 (동일포맷은 ConversionEngine.cs:88-89에서 무조건 Skip됨).",
"DocumentProvider.cs:24 `Outputs = {.html,.docx,.md,.txt}` 및 :40 RoadmapNote — HWP/HWPX가 출력 배열에서 영구 제외. 양방향 HWP를 위해서는 H2Orestart 쓰기 대체재(또는 자체 HWP writer)가 필요하나 코드 구조상 출력 라우트(RouteAsync, :92-205)에 .hwp 분기 자체가 없음.",
"PdfProvider.cs:11-12 PdfRenderOutputs에 .pdf·.docx 부재 — PDF→PDF(압축/병합) 및 PDF→편집문서 경로가 Provider 차원에서 차단. PDFtoImage 라이브러리는 렌더 전용이라 PDF 쓰기/조작 백엔드(예: PdfPig/QuestPDF/iText) 도입 전까지 확장 불가.",
"ConvertOptions.cs 전체 — 옵션 클래스가 이미지·PDF렌더·HTML렌더·OCR로 한정. Video/Audio/Archive/Ai 옵션 그룹이 없어, 미디어 코덱(비트레이트/해상도/fps)이나 LLM 프롬프트/모델 선택을 전달할 통로가 없음. 새 카테고리는 옵션 모델 확장이 선행되어야 함.",
"MagickProvider.cs:8-19, HeicProvider.cs:11-12, PdfProvider.cs:11-12 등 — 각 Provider가 지원 확장자 배열을 하드코딩. ffmpeg 같은 '수백 포맷 양방향' 백엔드를 PairsFromMatrix로 표현하면 조합 폭발(예: 50입력×50출력=2500쌍)로 레지스트리 메모리·UI 매트릭스가 비현실적 — 능력 기반(capability predicate) 표현이 없음.",
"ProviderRegistry.cs:22 `_byPair.TryAdd` — 동일 (input,output)에 복수 Provider(예: 빠른 변환 vs 고품질 vs AI 변환)를 '선택지'로 공존시키는 모델 부재. 한 쌍당 정확히 하나의 Provider만 허용해 변환 전략 다중화(품질/속도/AI 토글)가 불가능.",
"Everything2EverythingBootstrap.cs:11-21 — Provider 목록이 컴파일타임 하드코딩 배열. 플러그인/동적 등록(외부 DLL, ffmpeg 유무에 따른 조건부 등록)이 없어 신규 카테고리 추가가 항상 코어 재컴파일을 요구."
],
"improvementOpportunities": [
{
"title": "LibreOffice 호출 로직을 단일 SofficeRunner 서비스로 통합 + 타임아웃·직렬화 추가",
"rationale": "DocxProvider.ConvertWithLibreOfficeAsync(113-157), HwpxProvider.ConvertWithLibreOfficeAsync(107-151), DocumentProvider.SofficeConvertAsync(238-281)가 거의 동일한 ProcessStartInfo+--headless 인자+산출물 이동 로직을 3중 복제. 공용 서비스로 추출하면 중복 제거와 함께 (현재 전무한) 프로세스 타임아웃·동시성 락·재시도를 한 곳에서 추가해 soffice 행(hang)/프로필 충돌 안정성을 크게 높일 수 있음.",
"impact": "high",
"effort": "low"
},
{
"title": "이미지 인코딩(ApplyEncoding/ApplyTransforms) 공용 ImageEncoder 헬퍼로 추출",
"rationale": "jpg/png/webp/avif/tiff 인코딩 + 알파 평탄화 + MaxLongEdge 리사이즈 로직이 MagickProvider(144-208), PdfProvider(99-149), HtmlProvider(104-137), ConversionEngine.CombineAsync(215-257)에 4중 복제됨. 단일 헬퍼로 모으면 새 출력 포맷/품질 옵션을 한 번만 추가해 전 Provider가 일관되게 획득 — 현재는 한 곳만 고치면 나머지가 누락되는 드리프트 위험.",
"impact": "medium",
"effort": "low"
},
{
"title": "FFmpeg 기반 미디어 Provider(영상·오디오) 신설 + 능력기반 매트릭스 표현 도입",
"rationale": "프로젝트 목표의 핵심인 영상/오디오 코덱·압축·포맷 변환을 담당하는 Provider가 전무. ffmpeg는 양방향 N×M이 본질이므로, PairsFromMatrix 카르테시안 방식 대신 '입력군↔출력군 + 코덱 옵션'을 표현하는 capability predicate 모델을 함께 도입해야 조합 폭발을 피하면서 진짜 다방향을 달성. ExternalToolDetector에 ffmpeg 탐지 추가로 기존 의존성 패턴 재사용 가능.",
"impact": "high",
"effort": "high"
},
{
"title": "PDF 쓰기/조작 백엔드 도입으로 PDF→PDF(압축/병합/분할)·PDF→DOCX 경로 개통",
"rationale": "현재 PdfProvider는 렌더 전용이라 매트릭스 최대 갭이자 명시적 목표인 'PDF 압축'과 'PDF→편집문서' 역방향이 비어 있음. QuestPDF/PdfPig/Ghostscript 등 쓰기 백엔드를 추가하면 PdfProvider 출력 배열에 .pdf를 더해 압축·병합을 제공하고, LibreOffice 경유 PDF→DOCX 역라우트도 DocumentProvider에 추가 가능(소스가 PDF면 soffice가 draw로 변환). 매트릭스 양방향성을 가장 크게 끌어올리는 단일 작업.",
"impact": "high",
"effort": "high"
},
{
"title": "AI/LLM 변환 확장점 설계: IConverterProvider에 AI Provider 카테고리 + ConvertOptions.Ai 옵션 그룹",
"rationale": "Codex/LLM 통합 목표를 위한 추상화가 코드에 전혀 없음. 'OCR 후 LLM 교정', 'PDF→요약 MD', '이미지→캡션/번역', '문서 언어 번역' 같은 생성형 변환을 담을 AiProvider 인터페이스 확장과 옵션(모델/프롬프트/API키)을 먼저 설계하면, 레지스트리의 다중 Provider 공존 모델 개선과 맞물려 동일 (input,output) 쌍에 '일반/AI' 전략을 선택지로 노출 가능. 현 구조는 끼워넣을 자리조차 없어 선제적 설계가 필요.",
"impact": "high",
"effort": "high"
},
{
"title": "레지스트리 다중 Provider 공존 + 명시적 우선순위/충돌 경고 모델",
"rationale": "_byPair.TryAdd(ProviderRegistry.cs:22)가 동일 쌍 중복 시 후속 Provider를 조용히 버림. PDF압축 vs PDF렌더, AI변환 vs 일반변환처럼 한 쌍에 복수 전략이 필연적으로 생기는데 현재는 표현 불가하고 버그 유발. (input,output)→List<Provider>로 바꾸고 우선순위/품질태그를 부여하면 UI에서 변환 전략을 선택하게 하고, 등록 충돌을 진단창에 경고로 노출 가능.",
"impact": "medium",
"effort": "medium"
},
{
"title": "아카이브(zip/7z) 및 폰트(ttf↔woff2) Provider 추가로 빈 카테고리 보강",
"rationale": "아카이브 압축/해제와 웹폰트 변환은 비교적 독립적이고 성숙한 .NET 라이브러리(System.IO.Compression, SharpCompress, FontTools류)로 빠르게 채울 수 있는 저비용 갭. ComingSoon enum이 미사용 상태이므로, 단계적 출시를 위해 먼저 ComingSoon로 등록해 로드맵 UI를 실제로 활성화하는 부수 효과도 얻음.",
"impact": "medium",
"effort": "medium"
}
],
"keyFiles": [
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Providers/ProviderRegistry.cs",
"role": "(input,output)→Provider 평탄화 디스패치 테이블. TryAdd(:22)의 first-wins 정책이 향후 다중 전략 확장의 핵심 병목."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Providers/ProviderCapability.cs",
"role": "PairsFromMatrix(:62-71) 카르테시안 곱으로 변환 쌍 선언. ffmpeg류 대규모 매트릭스에서 조합 폭발 유발 지점이자 capability-predicate 도입 후보."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Providers/IConverterProvider.cs",
"role": "단일입력→단일출력 고정 인터페이스. 다방향/동일포맷최적화/AI 변환의 표현 한계 근원."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Everything2EverythingBootstrap.cs",
"role": "8개 Provider 하드코딩 등록 + 등록 순서 결정(:11-21). 플러그인/동적등록 부재 지점."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Converters/DocumentProvider.cs",
"role": "텍스트 5종 매트릭스 라우터(RouteAsync:92-205). HWP 입력전용·출력불가(:24,:40), LibreOffice 경유 라우팅의 중심."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Converters/PdfProvider.cs",
"role": "PDF→이미지 렌더 전용(:11-12). PDF 출력/압축/PDF→DOCX 역방향 부재의 핵심 갭. 다른 Provider들이 ConvertCore를 공용 백엔드로 재사용."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Converters/ExternalToolDetector.cs",
"role": "LibreOffice/Word COM/H2Orestart 탐지. ffmpeg 등 신규 외부도구 탐지 확장의 표준 패턴 제공."
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/ConvertOptions.cs",
"role": "이미지/PDF/HTML/OCR 한정 옵션 모델. Video/Audio/Archive/Ai 옵션 그룹 부재 — 신규 카테고리 확장의 선행 작업 대상."
}
]
},
{
"key": "app-ui",
"subsystem": "App UI / CLI / Shell (Everything2Everything WPF .NET 9)",
"summary": "WPF FluentWindow 기반 데스크톱 UI로, MainWindow가 큐 관리·매트릭스 출력 필터링·진행 표시·프리뷰·이력을 모두 코드비하인드(MainWindow.xaml.cs 1171줄)에서 직접 처리한다. 출력 형식은 큐 내 모든 입력의 OutputsForFile 교집합으로 1-hop 직접 변환만 필터링하며(RefreshAvailableOutputFormats:836), 멀티홉/체이닝/AI/미디어 개념은 코드 어디에도 없다. CLI(CliRouter)는 5개 verb를 파싱하지만 register/diagnose를 제외하면 모두 GUI 창을 띄우는 런처에 불과해 stdout/exit-code 기반 자동화·파이프라인이 불가능하다. ContextMenuRegistrar는 12개 PopularOutputs를 별도 하드코딩하여 ProviderRegistry 매트릭스와 부분적으로만 동기화된다.",
"strengths": [
"매트릭스 출력 필터링이 ProviderRegistry를 단일 진실원천(SSOT)으로 삼아 동적으로 동작 — 큐 변경 시마다 OutputsForFile 교집합을 재계산(RefreshAvailableOutputFormats MainWindow.xaml.cs:849-858)하므로 Provider만 추가하면 1-hop 변환 UI는 자동 확장된다",
"컨텍스트 메뉴 카스케이드가 OutputsForInput 매트릭스를 조회해 입력 확장자별로 가능한 출력만 노출(ContextMenuRegistrar.cs:37-45), 우클릭→'to <ext> %1' CLI 호출로 GUI 없이 즉시 변환되는 깔끔한 셸 통합",
"진행 표시·취소 UX가 IProgress<ConvertProgress> + CancellationTokenSource로 일관되게 구현 — 사이드바 전체 진행바(ProcessingProgressPanel)와 큐 row별 inline 진행바(QueueItem.ProgressValue)가 동시 갱신되고 취소 버튼이 즉각 상태를 'cancelling'으로 전환(OnCancelProcessingClick:619)",
"전역 예외 로깅(WireGlobalExceptionLogging App.xaml.cs:122)이 AppDomain/Dispatcher/TaskScheduler 3채널을 모두 후킹해 안정성 확보, Quick/Register 모드는 임시폴더에 타임스탬프 로그를 남겨 디버깅 가능",
"외부 도구 의존 Provider(RequiresExternal)를 시작 시 비동기 점검(RefreshCapabilityStatusAsync:66)해 준비 안 된 형식을 사이드바에 경고 표시 — 케이퍼빌리티 인식형 UI의 기반은 마련됨",
"CombineToSingle UX가 입력/출력 양쪽 조건(CanCombineInput/CanCombine)과 큐 개수를 종합해 버튼 활성화 및 5가지 분기 힌트 메시지 제공(UpdateCombineState:909-931)"
],
"weaknesses": [
"MVVM 전무: MainWindow.xaml.cs 1171줄에 View 로직·ViewModel(QueueItem/DateGroup/HistoryRow)·Service 호출(PreviewService/HistoryStorage/engine.ConvertManyAsync)·CSV/JSON 익스포트가 한 파일에 혼재. 명령은 일부 RelayCommand(25-38)지만 대부분 코드비하인드 이벤트 핸들러(OnProcessQueueClick 등)라 테스트·재사용 불가",
"변환 옵션 UI가 형식별로 확장 불가능한 구조: 사이드바에는 Quality 슬라이더 하나만 있고 ConvertOptions의 10여 종 옵션(PngCompression, AvifSpeed, PdfRender.Dpi, Ocr.Language, HtmlRender.Viewport 등)이 전혀 노출되지 않음. BuildOptions(259-281)는 JPEG/WebP/AVIF Quality만 슬라이더에서 읽고 나머지는 모두 기본값 하드코딩",
"CLI가 진정한 자동화에 부적합: dialog/quick/showmain 모드가 모두 WPF 창을 띄우며(App.xaml.cs:39-57), stdout으로 결과(출력 경로/성공·실패 카운트)를 반환하지 않고 exit code도 Quick 실패 시 1만 반환. JSON 출력·배치 매니페스트·표준입력 파이프·진행 스트리밍이 없어 AI/스크립트가 결과를 파싱 불가",
"출력 형식 매트릭스가 3곳에 중복 하드코딩: MainWindow.AllFormats(813-827, 12개), ContextMenuRegistrar.PopularOutputs(14-28, 12개), OpenFileDialog 입력 필터(96). 새 형식 추가 시 세 곳을 수동 동기화해야 하며 라벨·정렬·색상 리소스 키가 분산",
"멀티홉 경로 표현 수단이 UI/엔진 양쪽에 전무: ComboBox는 1-hop 직접 변환만 나열하고, 멀티홉이 생기면 '직접 vs 경유' 구분, 경로 미리보기(예: HWP→PDF→PNG), 중간 형식 선택, 품질 누적 손실 경고를 표현할 자리가 없음",
"QuickProgressWindow는 취소 버튼이 없어(취소 토큰을 ConvertManyAsync에 전달조차 안 함, App.xaml.cs:96) CLI/우클릭 경로의 긴 변환을 중단할 방법이 없음 — 메인 창과 취소 UX가 비대칭",
"케이퍼빌리티 상태가 시작 시 1회만 점검되고(RefreshCapabilityStatusAsync) 결과를 캐시하지 않아, 형식 선택 시점에 해당 출력이 외부 도구 부재로 실패할지 사전 차단하지 못함 — 변환 실행 후에야 실패 확인",
"데모 시드 데이터(SeedDemoHistory:667-708)가 프로덕션 코드에 하드코딩되어 첫 실행 시 가짜 14.2MB PNG 등을 이력에 주입, 통계(EST. SPACE SAVED)를 오염시킴"
],
"extensibilityBlockers": [
"MainWindow.xaml.cs:849-858 — RefreshAvailableOutputFormats가 OutputsForFile(1-hop 직접 변환)의 단순 교집합만 계산. 멀티홉 그래프 탐색(BFS/DFS over ConversionPair)이 없어 'HWP→DOCX 경유 PDF' 같은 간접 경로가 출력 목록에 절대 나타나지 않음",
"ProviderRegistry.cs:52-61 — OutputsForInput이 _outputsByInput 직접 매핑만 반환. 도달 가능 그래프(transitive closure)·경로 비용·중간 형식 개념이 없어 다방향 변환 확장의 근본 차단점",
"MainWindow.xaml.cs:963-976 — UpdateQualityPanelForFormat이 ext를 switch로 하드코딩(.jpg/.webp/.avif만 quality 패널 표시). 형식별 옵션 스키마가 Provider에서 선언적으로 오지 않아, 새 옵션(코덱·비트레이트·OCR 언어·LLM 프롬프트)을 추가하려면 XAML+코드비하인드를 직접 수정해야 함",
"MainWindow.xaml.cs:259-281 — BuildOptions가 ConvertOptions의 일부 필드만 수동 채움. 미디어/AI Provider가 요구할 옵션(영상 코덱, CRF, 프레임레이트, AI 모델명, API 키)을 받을 동적 옵션 바인딩 메커니즘 부재",
"IConverterProvider.cs:9-15 — ConvertAsync 시그니처가 단일 sourcePath→단일 outputExtension 동기 변환 전제. 스트리밍 입력, 다중 출력 산출물(예: 영상→썸네일+자막+트랜스코드), AI 비동기 잡, 진행 중 부분 결과를 표현할 수 없음",
"ContextMenuRegistrar.cs:14-28 — PopularOutputs 배열이 정적이라 AI/미디어 형식(.mp4/.webm 등) 추가 시 카스케이드에 자동 반영되지 않고, 멀티홉 출력도 우클릭 메뉴에 노출 불가",
"App.xaml.cs:39-57 & CliRouter.cs:21-49 — CLI 파서가 옵션 플래그(--quality, --output-dir, --json, --recursive)를 전혀 받지 않고 verb+files 구조만 지원. AI 통합(LLM 프롬프트 전달)·배치 스크립팅을 위한 인자 확장 여지가 구조적으로 막힘"
],
"improvementOpportunities": [
{
"title": "엔진에 변환 그래프 + 멀티홉 경로 탐색 도입",
"rationale": "ProviderRegistry에 ConversionPair 그래프를 구성하고 BFS로 도달 가능 출력·최단 경로를 계산하는 PathFinder를 추가. RefreshAvailableOutputFormats(MainWindow.xaml.cs:836)가 직접+간접 출력을 모두 나열하고, 선택 시 경로(HWP→PDF→PNG)를 미리보기로 보여주면 '진짜 다방향 변환 극대화' 목표의 핵심 기반이 된다. ConvertManyAsync는 경로를 받아 중간 산출물을 임시폴더에 체이닝.",
"impact": "high",
"effort": "high"
},
{
"title": "Provider 선언형 옵션 스키마 + 동적 옵션 UI 생성",
"rationale": "ProviderCapability에 옵션 디스크립터(이름/타입/범위/기본값/조건)를 선언하고, 사이드바가 선택된 출력 형식에 맞춰 컨트롤을 동적 생성하도록 전환. 현재 하드코딩된 UpdateQualityPanelForFormat(963)·BuildOptions(259)를 대체하면 미디어 코덱·OCR 언어·AI 프롬프트 등 어떤 옵션도 코드 수정 없이 노출 가능.",
"impact": "high",
"effort": "high"
},
{
"title": "헤드리스 CLI 모드 분리 (stdout JSON + exit code + 옵션 플래그)",
"rationale": "to/quick 모드에 --headless/--json을 추가해 WPF 창 없이 변환하고, 결과(출력 경로·바이트·성공·실패)를 JSON으로 stdout 출력, 적절한 exit code 반환. CliRouter.Parse(21)를 플래그 파싱으로 확장하고 ConsoleHelper로 스트리밍. AI 에이전트·배치 스크립트·CI 파이프라인 통합의 전제 조건이며 effort 대비 자동화 가치가 매우 큼.",
"impact": "high",
"effort": "medium"
},
{
"title": "출력 형식 매트릭스 단일 SSOT로 통합",
"rationale": "AllFormats(MainWindow:813)·PopularOutputs(ContextMenuRegistrar:14)·OpenFileDialog 필터(96)의 3중 중복을 Core의 단일 FormatCatalog(확장자·라벨·색상키·정렬)로 통합하고 ProviderRegistry와 교차검증. 새 형식 추가 시 한 곳만 수정하면 UI·우클릭·파일다이얼로그가 동시 반영되어 미디어/AI 형식 추가 비용이 급감.",
"impact": "medium",
"effort": "low"
},
{
"title": "MainWindow를 MVVM으로 분해",
"rationale": "1171줄 코드비하인드를 MainWindowViewModel + 서비스(QueueService/HistoryService/PreviewService 호출)로 분리하고 RelayCommand를 전면 적용. 멀티홉·AI·미디어 기능이 추가될수록 코드비하인드 비대화가 가속되므로, 지금 분해해야 형식별 옵션 패널·경로 선택 등 신규 UI를 독립 테스트 가능한 단위로 붙일 수 있다.",
"impact": "medium",
"effort": "high"
},
{
"title": "QuickProgressWindow에 취소 + 케이퍼빌리티 사전 점검",
"rationale": "App.RunQuickAsync(82)가 CancellationTokenSource를 만들어 ConvertManyAsync에 전달하고 QuickProgressWindow에 취소 버튼을 추가해 CLI/우클릭 경로의 긴 변환(특히 미래의 영상·AI 잡)을 중단 가능하게. 또한 형식 선택 시점에 CheckAvailabilityAsync 캐시를 조회해 외부도구 부재 출력을 사전 비활성화하면 실패 후 확인하는 현재 UX를 개선.",
"impact": "medium",
"effort": "medium"
}
],
"keyFiles": [
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/Views/MainWindow.xaml.cs",
"role": "메인 UI 코드비하인드(1171줄): 큐 관리, 매트릭스 출력 필터링(RefreshAvailableOutputFormats:836), 옵션 빌드(BuildOptions:259), 진행/취소, 프리뷰, 이력, CSV/JSON 익스포트가 모두 혼재 — MVVM 부재의 진앙"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/Cli/CliRouter.cs",
"role": "CLI 파서: verb+files만 인식, 옵션 플래그·JSON 출력·stdout 결과 없음 — 자동화/AI 통합 차단점"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/App.xaml.cs",
"role": "진입점: CLI 모드를 GUI 창 생성으로 라우팅(39-57), 헤드리스 실행 경로 부재, 전역 예외 로깅"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/Shell/ContextMenuRegistrar.cs",
"role": "우클릭 카스케이드 등록: PopularOutputs 12개 하드코딩(14-28)을 OutputsForInput 매트릭스와 교차해 입력별 메뉴 생성 — 매트릭스 동기화가 부분적"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Providers/ProviderRegistry.cs",
"role": "변환 매트릭스 SSOT: _byPair/_outputsByInput 직접 매핑만 보유(52-61), 그래프 탐색·멀티홉 도달성 부재 — 다방향 확장의 근본 차단점"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/ConversionEngine.cs",
"role": "변환 실행: ConvertOneAsync는 1-hop 직접 변환만(91), Combine은 이미지→PDF/TIFF/GIF 특수 경로, 멀티홉 체이닝 없음"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/ConvertOptions.cs",
"role": "형식별 옵션 모델(10여 종): UI에서 Quality 3종만 바인딩되고 나머지는 미노출 — 옵션 UI 확장성 격차의 증거"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Providers/IConverterProvider.cs",
"role": "Provider 계약: 단일입력→단일출력 ConvertAsync(9), 다중산출물·스트리밍·AI 비동기잡·선언형 옵션 스키마 표현 불가"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/Views/QuickProgressWindow.xaml.cs",
"role": "CLI/우클릭용 진행 창: 취소 버튼·취소 토큰 부재(메인 창과 비대칭), 결과 요약만 표시"
}
]
},
{
"key": "infra",
"subsystem": "인프라 (히스토리 / 프리뷰 / 빌드 / 패키징 / CI-CD / 설정 영속화)",
"summary": "히스토리는 `%LocalAppData%`에 append-only JSONL로 저장하는 정적 헬퍼(HistoryStorage)와 UI 전용 인메모리 컬렉션(HistoryStore)으로 단순 분리돼 있고, 프리뷰는 이미지/PDF/HEIC만 동기 렌더하며 문서·영상은 명시적으로 스텁 처리한다. 빌드/패키징은 framework-dependent MSIX 단일 산출물에 최적화돼 있어 .NET DLL과 C++ Shell DLL만 Layout에 복사하고 FFmpeg/Ghostscript 같은 대형 외부 바이너리 번들링 메커니즘이 전혀 없다. 가장 큰 구조적 공백은 설정 영속화 계층의 완전 부재로, 변환 옵션은 매번 UI에서 재구성되고(BuildOptions) AI API 키·도구 경로·사용자 기본값을 저장할 곳이 없으며, 테스트 인프라도 0이다.",
"strengths": [
"히스토리 저장이 손상 내성을 가진 JSONL(줄 단위) 포맷이라 한 줄이 깨져도 나머지는 로드됨 — HistoryStorage.cs:30-39의 라인별 try/catch로 파싱 격리. 영상/배치 같은 대량 엔트리가 쌓여도 append-only라 쓰기 비용은 O(1)",
"HistoryStorage의 모든 I/O가 try/catch로 감싸져 영속화 실패가 인메모리 동작을 막지 않음(HistoryStorage.cs:56-59, 64) — 권한 없는 환경/네트워크 드라이브에서도 앱이 죽지 않는 graceful degradation",
"PreviewService가 CancellationToken을 끝까지 전파하고 무거운 디코딩을 Task.Run으로 오프로드(PreviewService.cs:25-30) — UI 스레드 블로킹 방지, 빠른 큐 전환 시 이전 프리뷰 취소 가능",
"ExternalToolDetector(ExternalToolDetector.cs:7-37)가 ProgramFiles 다중 경로 + 레지스트리(UNO\\InstallPath) 폴백으로 외부 도구를 탐지하는 견고한 패턴을 이미 보유 — FFmpeg/Ghostscript 탐지로 그대로 확장 가능한 선례",
"출력 디렉토리 전략이 enum(OutputLocation)으로 추상화돼 서브폴더/동일폴더/커스텀 3종을 깔끔히 분기(ConversionEngine.cs:259-282), suffix가 출력 확장자 기반 자동 생성(`_pdf`, `_png`)이라 양방향 매트릭스 확장 시 충돌 없음",
"CI가 build.yml(PR 검증)과 release.yml(태그 기반 릴리즈 + 조건부 PFX 서명)로 역할 분리돼 있고, release.yml:35-49가 secret 유무에 따라 서명/미서명을 graceful하게 분기"
],
"weaknesses": [
"설정 영속화 계층이 전무함 — 저장되는 영구 상태는 history.jsonl 단 하나. ConvertOptions는 MainWindow.BuildOptions()(MainWindow.xaml.cs:259-281)에서 매 실행마다 UI 컨트롤로부터 새로 생성되고 _conflictRule·QualitySlider 값은 앱 재시작 시 소실. AI(Codex/LLM) API 키, LibreOffice/FFmpeg 경로, 사용자 기본 출력 형식을 저장할 메커니즘이 없어 AI 통합·도구 경로 캐싱의 토대가 없음",
"PreviewService가 문서·영상을 원천 미지원 — 문서는 '다음 업데이트에서 지원'(PreviewService.cs:28-29), 영상은 case 자체가 없어 RenderViaMagick(PreviewService.cs:30)로 폴백→MagickImage가 mp4/mov를 못 열어 예외. HWP↔DOCX, 영상 코덱 변환 목표 대비 프리뷰가 핵심 입력 타입을 커버 못함",
"프리뷰 캐싱 부재 — 같은 파일을 다시 선택할 때마다 디코딩/리사이즈/temp PNG 왕복(RenderHeic의 PreviewService.cs:67-76)을 재수행. 대용량/배치 큐에서 동일 항목 반복 클릭 시 매번 풀 디코딩",
"히스토리 전체 로드가 비확장적 — File.ReadAllLines(HistoryStorage.cs:27)로 전 파일을 메모리에 적재 후 OrderBy(MainWindow.xaml.cs:663)로 전량 정렬. 영상/배치 변환으로 엔트리가 수만 줄 쌓이면 시작 시 전부 파싱·정렬해야 함(페이징·tail read·만료 정책 없음)",
"히스토리 도메인 로직이 UI에 결합 — 로드/그룹화/데모 시드(SeedDemoHistory MainWindow.xaml.cs:667-708)가 MainWindow.xaml.cs에 박혀 있고, 실측 데이터가 없으면 가짜 '842.4 MB 절약' 데모가 통계에 섞임(MainWindow.xaml.cs:685,704). 헤드리스/CLI 배치 경로에서 히스토리 기록 재사용 불가",
"빌드/패키징이 대형 외부 바이너리 번들을 전혀 가정하지 않음 — BuildMsix.ps1:108-109가 publish 산출물 + Shell DLL만 Layout에 복사. FFmpeg(~80MB)/Ghostscript를 동봉하려면 Layout 복사 단계, MSIX 용량(현재 200MB+ 압축 한계 고려), 라이선스(FFmpeg LGPL/GPL) 처리 로직이 모두 없음",
"테스트 인프라 0 — 솔루션에 테스트 프로젝트가 없음(Glob **/*Test* 무결과). OutputPathHelper 충돌 해결, ConversionEngine 결합 로직, JSONL round-trip 같은 순수 함수조차 자동 검증 없어 양방향 매트릭스 확장 시 회귀 탐지 불가",
"CI가 self-contained/portable EXE를 빌드·게시하지 않음 — README는 portable EXE를 'A) 가장 가벼움'으로 권장하나(README.md:140), 두 워크플로 모두 MSIX만 산출(build.yml:42, release.yml:75). .NET 9 Desktop Runtime 미설치 환경용 self-contained 배포 산출물이 자동화에 없음",
"CI에 NuGet/빌드 캐시가 없어(build.yml 전체) 매 실행마다 Magick.NET·WebView2 등 대형 패키지를 재복원 — 외부 바이너리까지 더해지면 빌드 시간 선형 증가"
],
"extensibilityBlockers": [
"PreviewService.cs:23-31 — 확장자 switch가 닫힌 구조이고 default가 RenderViaMagick(MagickImage)로 폴백. 영상(.mp4/.mov/.mkv) 프리뷰를 추가하려면 FFmpeg 프레임 추출 case를 직접 삽입해야 하며, 현재는 영상 입력 시 MagickImage 생성자에서 예외 발생",
"PreviewService 전체가 static 클래스 + 하드코딩된 디코더 의존성(ImageMagick/PDFtoImage/Libheif) — 영상/AI 썸네일을 위한 디코더 플러그인 주입(DI) 지점이 없어 IPreviewRenderer 추상화 없이는 확장 불가",
"ConvertOptions(ConvertOptions.cs:17-58)에 영상(코덱/비트레이트/fps)·AI(모델/프롬프트/API키)·압축(PDF 압축 레벨) 옵션 sub-record가 없고, 영속화 대상이 아니라 [JsonSerializable] 직렬화 속성도 없음 — AI 기능 설정과 도구 경로를 담을 영구 설정 모델이 부재",
"BuildMsix.ps1:102-115 Layout 구성 단계가 publish 출력 + 단일 Shell DLL만 복사하도록 하드코딩 — FFmpeg/Ghostscript/Tesseract 같은 외부 바이너리를 동봉하는 Copy-Item 단계와 그 경로를 런타임에 해석하는 메커니즘이 없음",
"Package.appxmanifest:175-177이 runFullTrust 단일 capability만 선언 — AI 기능의 네트워크 호출(internetClient)이나 추가 파일 타입(.mp4/.mov/.webm/.mkv) ItemType 등록(현재 manifest:69-168에 영상 확장자 전무)이 없어 영상 우클릭 메뉴 노출 불가",
"HistoryEntry(HistoryStore.cs:5-15)가 영상/배치 변환 메타데이터(코덱, 해상도, duration, 다중 출력 통계)를 표현할 필드가 부족 — MetaLine 단일 문자열에 의존(HistoryStore.cs:13)해 구조화된 영상 변환 이력 질의 불가",
"설정/키 저장소가 없어 AI API 키를 DPAPI(ProtectedData)로 암호화 저장할 진입점 자체가 부재 — HistoryStorage.cs의 LocalAppData 패턴은 있으나 평문 JSONL이라 비밀 저장에 부적합"
],
"improvementOpportunities": [
{
"title": "ISettingsStore(설정 영속화 계층) 도입 — DPAPI 암호화 + JSON",
"rationale": "AI(Codex/LLM) API 키, 외부 도구 경로(FFmpeg/Ghostscript/LibreOffice 캐시), 사용자 기본 ConvertOptions를 %LocalAppData%에 영속화. 비밀(API 키)은 System.Security.Cryptography.ProtectedData(DPAPI)로 암호화. HistoryStorage.cs의 LocalAppData+try/catch 패턴을 그대로 재사용하면 일관성 확보. AI 통합·도구 경로 캐싱·옵션 기억의 공통 토대가 되어 가장 높은 레버리지를 가짐.",
"impact": "high",
"effort": "medium"
},
{
"title": "IPreviewRenderer 추상화 + 영상 프레임 추출(FFmpeg) + 프리뷰 캐시",
"rationale": "PreviewService.cs:23-31의 닫힌 switch를 확장자→IPreviewRenderer 레지스트리로 교체하고, 영상은 FFmpeg로 중간 프레임을 추출해 썸네일화. (path,mtime,maxEdge) 키로 LRU 캐시를 두면 배치 큐 반복 클릭 시 재디코딩 제거. 영상 변환 목표의 전제 조건이며 프리뷰 미지원 입력(문서/영상)의 핵심 공백을 메움.",
"impact": "high",
"effort": "high"
},
{
"title": "외부 바이너리 번들링 파이프라인 (BuildMsix.ps1 확장 + ExternalToolDetector 폴백)",
"rationale": "BuildMsix.ps1:102-115 Layout 단계에 FFmpeg/Ghostscript를 다운로드·검증(체크섬)·복사하는 단계를 추가하고, ExternalToolDetector.cs에 '번들 경로 우선, 없으면 시스템 탐지' 폴백을 더함. MSIX 용량 증가 대비 self-contained와 별개 산출물로 분리. 영상/PDF 압축 기능 구현의 필수 인프라.",
"impact": "high",
"effort": "high"
},
{
"title": "히스토리 영속화의 스트리밍 로드 + 만료/회전 정책",
"rationale": "HistoryStorage.cs:27의 File.ReadAllLines 전량 적재를 마지막 N줄 tail-read 또는 페이징으로 교체하고, 줄 수/일수 기반 회전(rotation)을 추가. 영상/배치로 이력이 수만 줄 쌓일 때 시작 지연을 방지. HistoryEntry에 구조화된 영상 메타(코덱/해상도/duration) 필드 추가도 병행.",
"impact": "medium",
"effort": "medium"
},
{
"title": "히스토리 도메인 로직을 UI에서 Core로 분리 + 데모 시드 제거",
"rationale": "LoadHistory/AddToHistoryGroups/SeedDemoHistory(MainWindow.xaml.cs:652-708)가 UI에 결합돼 CLI/헤드리스 배치 경로에서 이력 기록을 재사용 못함. HistoryService를 Core로 올리고 가짜 통계(MainWindow.xaml.cs:685,704)를 제거하면 배치 파이프라인·테스트·정확한 통계가 모두 가능.",
"impact": "medium",
"effort": "low"
},
{
"title": "테스트 프로젝트 신설 (xUnit) — 순수 함수 우선",
"rationale": "OutputPathHelper 충돌 해결(AppendNumber/Skip/Overwrite 경계), ConversionEngine.IsCombineSupported, HistoryEntry JSONL round-trip, ExternalToolDetector 경로 우선순위 등 외부 의존성 없는 순수 로직부터 커버. 양방향 N×M 매트릭스가 200+ 쌍으로 늘 때 회귀 안전망 확보. CI(build.yml)에 dotnet test 단계 1줄 추가로 게이트화.",
"impact": "high",
"effort": "medium"
},
{
"title": "CI 강화 — NuGet 캐시 + self-contained portable 산출물 + 테스트 게이트",
"rationale": "build.yml에 actions/cache(NuGet) 추가로 빌드 시간 단축(외부 바이너리 추가 시 효과 증대), README가 권장하는 portable EXE(README.md:140)를 self-contained로 빌드해 release.yml 산출물에 동봉(.NET 런타임 미설치 환경 지원), dotnet test 단계로 PR 품질 게이트. 현재 두 워크플로(build.yml/release.yml) 모두 MSIX만 산출하는 공백 해소.",
"impact": "medium",
"effort": "low"
},
{
"title": "OutputPathHelper 충돌 처리 강건화 + 결합 출력 경로 일관화",
"rationale": "OutputPathHelper.cs:27의 10000회 선형 File.Exists 스캔은 동일 폴더 대량 출력(영상 프레임 추출 시 수천 장) 시 O(n^2)에 가깝게 느려짐 — 디렉토리 1회 열거 후 최대 인덱스+1 방식으로 개선. Overwrite/Skip이 모두 fullPath를 반환(OutputPathHelper.cs:21-24)하는 점은 ShouldSkip와 분리돼 있어 호출자가 둘 다 호출해야 하는 암묵 계약 — 의도를 한 메서드로 응집.",
"impact": "low",
"effort": "low"
}
],
"keyFiles": [
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/HistoryStorage.cs",
"role": "유일한 영구 상태 저장소 — append-only JSONL, 손상 내성 라인별 파싱, 전량 로드(비페이징). 설정 영속화 계층의 재사용 가능한 패턴 원천"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/HistoryStore.cs",
"role": "인메모리 ObservableCollection + HistoryEntry record. 영상/배치 메타데이터 필드 부족(MetaLine 단일 문자열 의존)"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/PreviewService.cs",
"role": "static 프리뷰 렌더러 — 이미지/PDF/HEIC만 지원, 문서·영상 스텁/미지원, 캐싱 없음. 영상/AI 확장의 핵심 블로커"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/OutputPathHelper.cs",
"role": "출력 경로/충돌 해결 — 10000회 선형 스캔, Overwrite/Skip 동일 반환 + ShouldSkip 분리 계약"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/ConvertOptions.cs",
"role": "형식별 옵션 sub-record 집합 — 영상/AI/압축 옵션 부재, 직렬화/영속화 미지원"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.Core/Converters/ExternalToolDetector.cs",
"role": "ProgramFiles+레지스트리 폴백 도구 탐지 — FFmpeg/Ghostscript 탐지로 확장 가능한 선례, 번들 경로 폴백 추가 지점"
},
{
"path": "D:/workspace/Everything2Everthing/packaging/BuildMsix.ps1",
"role": "MSIX 빌드 파이프라인 — Layout에 publish+Shell DLL만 복사(BuildMsix.ps1:108-109), 외부 바이너리 번들 단계 없음"
},
{
"path": "D:/workspace/Everything2Everthing/packaging/Package.appxmanifest",
"role": "MSIX 매니페스트 — runFullTrust만 선언, 영상 확장자/네트워크 capability 미등록"
},
{
"path": "D:/workspace/Everything2Everthing/.github/workflows/build.yml",
"role": "PR CI — 캐시·테스트·portable 산출물 없음, MSIX만 빌드"
},
{
"path": "D:/workspace/Everything2Everthing/.github/workflows/release.yml",
"role": "릴리즈 CI — 태그 기반, 조건부 PFX 서명, MSIX만 산출(self-contained 없음)"
},
{
"path": "D:/workspace/Everything2Everthing/src/Everything2Everything.App/Views/MainWindow.xaml.cs",
"role": "히스토리 도메인 로직(LoadHistory/SeedDemoHistory:652-708)과 옵션 빌드(BuildOptions:259-281)가 UI에 결합 — 영속화·재사용 차단"
}
]
},
{
"key": "quality",
"subsystem": "횡단 품질 (에러 처리 / 동시성 / 취소 / 보안 / 메모리 / 테스트)",
"summary": "변환 파이프라인의 횡단 관심사는 비교적 일관된 패턴(IProgress 보고, OperationCanceledException 재던짐, try/finally 임시정리, ConvertResult.Fail 래핑)으로 손코딩되어 있고, 외부 프로세스 호출은 ProcessStartInfo.ArgumentList를 사용해 셸 인젝션을 구조적으로 회피한다. 그러나 배치 변환이 전적으로 순차 for-loop(ConversionEngine.cs:57)라 멀티코어/외부프로세스 대기 시간을 전혀 활용하지 못하고, 취소 토큰 전파가 라이브러리 경계(Magick.NET, Windows OCR, CDP)에서 끊기며, 메모리는 전 페이지/전 프레임을 한꺼번에 디코딩하는 비스트리밍 구조다. 테스트 프로젝트가 전무(0개)하고 NuGet 취약점 경고가 csproj에서 통째로 억제(NU1901-1904)되어, AI/영상/대용량 기능으로 확장하기 전에 품질 안전망이 먼저 필요하다.",
"strengths": [
"외부 프로세스 호출이 전부 ProcessStartInfo.ArgumentList 기반(DocumentProvider.cs:254-261, HwpxProvider.cs:118-125, DocxProvider.cs:124-131)이라 경로에 공백/특수문자/`&`가 있어도 셸 인젝션이 구조적으로 불가능하다. UseShellExecute=false + CreateNoWindow=true로 일관됨.",
"취소 시 외부 프로세스를 확실히 죽이는 패턴이 일관적이다: WaitForExitAsync(ct) → catch(OperationCanceledException){ proc.Kill(true); throw; } (DocumentProvider.cs:266-267, HwpxProvider.cs:134-137, DocxProvider.cs:140-143). Kill(true)로 자식 트리까지 종료.",
"임시 파일/디렉터리를 Guid 기반 고유 이름으로 만들고 try/finally로 정리한다(OcrProvider.cs:174, DocumentProvider.cs:71/88, HwpxProvider.cs:70/103, DocxProvider.cs:57/109). 동시 실행 시 충돌이 없도록 Guid.NewGuid():N를 씀.",
"ConversionEngine.ConvertOneAsync가 OperationCanceledException은 재던지고 그 외 예외만 ConvertResult.Fail로 래핑(ConversionEngine.cs:117-124)해, 취소와 실패가 의미상 구분된다. 한 파일 실패가 배치 전체를 죽이지 않는 격리 구조.",
"전역 예외 안전망 3종(AppDomain.UnhandledException, DispatcherUnhandledException, TaskScheduler.UnobservedTaskException)을 App 시작 시 배선하고 temp 로그에 append (App.xaml.cs:122-154). UI 스레드 미처리 예외는 사용자에게 메시지박스로 노출 후 Handled 처리.",
"HTML 렌더링을 전용 STA 스레드 + 독립 Dispatcher로 격리하고 finally에서 controller.Close() + Dispatcher 셧다운(HtmlProvider.cs:162-181, 245-249)해 WebView2 COM 자원 수명을 명확히 관리한다."
],
"weaknesses": [
"배치가 100% 순차 for-loop(ConversionEngine.cs:57-70)다. LibreOffice/OCR/WebView2처럼 대기 시간이 긴 변환에서 단일 파일씩만 처리해 멀티코어를 전혀 못 쓴다. 100장 이미지 변환도 코어 1개만 사용.",
"취소 토큰이 라이브러리 경계에서 단절된다. Magick.NET의 image.Write/Resize/collection.Coalesce(MagickProvider.cs:84-94, PdfProvider.cs:84-87)는 ct를 받지 않아, 거대 이미지 1장 인코딩 중에는 ThrowIfCancellationRequested 체크 지점 사이에서 취소가 지연된다. Windows OCR engine.RecognizeAsync(OcrProvider.cs:164)도 ct 미전달.",
"CLI quick 경로가 아예 취소 불가능하다: App.RunQuickAsync가 ConvertManyAsync를 CancellationToken 없이 호출(App.xaml.cs:96)하고 QuickProgressWindow에 취소 UI도 없다. 컨텍스트 메뉴 대량 변환을 중단할 방법이 없음.",
"에러 메시지 채널이 비일관적이다. 코어는 ConvertResult.Message(구조화)로 반환하지만, MainWindow는 catch에서 MessageBox.Show(ex.Message)로만 노출(MainWindow.xaml.cs:569)하고 OnProcessQueueClick은 개별 result.Status가 Failed/Skipped여도 그냥 히스토리에만 적고(MainWindow.xaml.cs:551-561) 사용자에게 실패를 표면화하지 않는다. 실패 파일도 done처럼 큐에서 제거됨(line 563).",
"메모리가 비스트리밍이다. PDF는 페이지마다 PNG 전체를 MemoryStream에 디코딩(PdfProvider.cs:79-87), OCR은 파일을 MemoryStream→ToArray()→InMemoryRandomAccessStream으로 3중 복사(OcrProvider.cs:148-159), HTML 캡처는 base64 PNG 전체를 메모리에 들고 Magick으로 재디코딩(HtmlProvider.cs:76-98). 8000x8000 같은 대용량/멀티프레임 GIF는 collection.Coalesce()로 전 프레임을 동시에 메모리에 적재(MagickProvider.cs:84).",
"ImageMagick 리소스 한계(ResourceLimits.Memory/Width/Height)가 어디에도 설정돼 있지 않다. 악의적/손상 이미지(decompression bomb)나 거대 RAW가 프로세스 메모리를 무제한 점유 가능. MagickProvider/PdfProvider/HtmlProvider/CombineAsync 전부 무방비.",
"테스트가 0개다(test 프로젝트/파일 없음 — Glob *Test* 결과 없음). 12종×N 양방향 매트릭스, 라우팅 분기(DocumentProvider.RouteAsync), 충돌 규칙, 취소 경로 모두 회귀 검증이 불가능.",
"csproj가 NuGet 취약점 경고를 통째로 억제한다: NoWarn에 NU1901;NU1902;NU1903;NU1904 (Everything2Everything.Core.csproj:11). 알려진 CVE가 있는 패키지가 들어와도 빌드가 침묵한다. WebView2/OpenXML/Magick은 외부 미디어를 파싱하는 공격면이 큰 라이브러리들이라 위험.",
"_cts 접근에 동시성 보호가 없다. OnProcessQueueClick은 _cts.Token을 await 호출 인자로 직접 읽고(MainWindow.xaml.cs:541) finally에서 _cts=null로 set(line 574)하는데, OnCancelProcessingClick(line 621-622)이 다른 시점에 _cts.Cancel()을 호출한다. UI 스레드 단일 진입으로 대체로 안전하나 _cts 수명/dispose가 명시적이지 않고 CancellationTokenSource.Dispose()가 한 번도 호출되지 않음(누수)."
],
"extensibilityBlockers": [
"ConversionEngine.cs:57-70 — ConvertManyAsync의 순차 for-loop가 하드코딩됨. 영상 트랜스코딩(파일당 수십 초~분)이나 AI 호출(LLM 왕복 지연)을 추가하면 순차 처리가 치명적 병목이 된다. 병렬도(MaxDegreeOfParallelism) 옵션이 ConvertOptions에 없음(ConvertOptions.cs 전체).",
"IProgress<double> 단일 스칼라 진행 모델(IConverterProvider.ConvertAsync 시그니처)은 영상(프레임/시간코드), AI(토큰 스트리밍), 다단계 파이프라인의 진행을 표현 못 한다. ConvertProgress(Index,Total,CurrentPath,FileProgress) (ConversionEngine.cs:285)도 단일 파일=단일 출력 가정에 묶여 있어 1→N 페이지 분할의 부분 진행을 못 담는다.",
"ConvertResult가 동기 완료 모델(ConvertResult.cs:10-25)이라 스트리밍/증분 출력(영상 인코딩 중 부분 미리보기, LLM 토큰 스트림)을 표현할 타입이 없다. OutputPaths는 변환 끝난 뒤에야 채워짐.",
"외부 프로세스 실행 로직(ConvertWithLibreOfficeAsync)이 DocxProvider/HwpxProvider/DocumentProvider에 거의 동일하게 3중 복제됨(DocxProvider.cs:113-157, HwpxProvider.cs:107-151, DocumentProvider.cs:238-281). FFmpeg/ghostscript(PDF압축)/codex CLI 같은 새 외부도구를 추가할 때마다 stderr 수집·타임아웃·종료처리·결과검증 보일러플레이트를 또 복붙해야 한다. 공통 ExternalProcessRunner 추상화 부재.",
"외부 프로세스에 타임아웃이 없다(WaitForExitAsync(ct)만, DocumentProvider.cs:266 등). LibreOffice/Word COM(DocxProvider.cs:159-186)이 hang하면 취소하기 전까지 영원히 대기. 영상/대용량 작업에선 walltime 한계가 필수.",
"stderr를 RedirectStandardError=true로 켜두고도 한 번도 읽지 않는다(DocumentProvider.cs:252, HwpxProvider.cs:116, DocxProvider.cs:122). 파이프 버퍼가 가득 차면 자식 프로세스가 블록될 수 있고, 실패 시 LibreOffice의 실제 오류 사유를 버려 ConvertResult가 'exit N'만 남긴다(DocumentProvider.cs:270). AI/코덱 도구 디버깅이 불가능."
],
"improvementOpportunities": [
{
"title": "배치 변환에 제한된 병렬 처리(Parallel.ForEachAsync) 도입",
"rationale": "ConvertManyAsync의 순차 for-loop(ConversionEngine.cs:57)를 MaxDegreeOfParallelism 옵션을 가진 Parallel.ForEachAsync로 교체하면 멀티코어 + 외부프로세스 대기 시간을 활용해 처리량이 대폭 향상된다. 단, IProgress 보고를 인덱스 기반에서 '완료 카운터'(Interlocked) 기반으로 바꾸고, MainWindow의 snapshot[i] 인덱스 매핑(MainWindow.xaml.cs:525-530)을 QueueItem별 개별 progress로 재설계해야 한다. 영상/AI 확장의 선결 과제.",
"impact": "high",
"effort": "medium"
},
{
"title": "공통 ExternalProcessRunner 추상화 (타임아웃 + stderr 수집 + Kill 통합)",
"rationale": "세 Provider에 복제된 LibreOffice 실행 코드(DocxProvider.cs:113, HwpxProvider.cs:107, DocumentProvider.cs:238)를 하나의 헬퍼로 통합하고, 거기에 walltime 타임아웃·stderr 비동기 읽기·실패 시 stderr를 ConvertResult.Message에 포함하는 기능을 추가한다. FFmpeg/ghostscript/codex CLI 등 향후 외부도구를 일관되게 붙일 토대가 되고, 현재의 stderr 미수집 데드락 위험과 hang 위험을 동시에 해소한다.",
"impact": "high",
"effort": "medium"
},
{
"title": "ImageMagick ResourceLimits 전역 설정 + decompression bomb 방어",
"rationale": "부트스트랩(Everything2EverythingBootstrap.cs)에서 ResourceLimits.Memory/Width/Height/Area에 합리적 상한을 설정해 손상·악성 이미지나 거대 RAW가 프로세스를 OOM시키는 것을 막는다. 신뢰할 수 없는 입력을 파싱하는 변환기의 기본 안전장치이며, 대용량 영상/이미지 기능 추가 시 더 중요해진다.",
"impact": "high",
"effort": "low"
},
{
"title": "코어 단위 테스트 프로젝트 신설 (xUnit) — 순수 로직 우선",
"rationale": "외부 의존성 없이 결정적으로 검증 가능한 부분이 풍부하다: OutputPathHelper(충돌/AppendNumber/Sanitize, OutputPathHelper.cs), CliRouter.Parse(CliRouter.cs:21), DocumentProvider 라우팅 분기(별칭 정규화·NotSupported 경로, DocumentProvider.cs:92-204), ConversionEngine.IsCombineSupported. Markdig/ReverseMarkdown round-trip(md→html→md)도 인메모리로 검증 가능. 12종 매트릭스 회귀를 막는 최소 안전망.",
"impact": "high",
"effort": "medium"
},
{
"title": "실패/건너뜀 결과를 UI에 일관되게 표면화",
"rationale": "OnProcessQueueClick(MainWindow.xaml.cs:543-564)이 result.Status가 Failed여도 done처럼 큐에서 제거하고 사용자에게 알리지 않는다. result.Status별로 QueueItem 상태(failed/skipped)를 시각화하고, 실패 항목은 큐에 남기거나 요약 토스트로 보고하도록 바꿔 '조용한 실패'를 없앤다. ConvertResult.Message가 이미 구조화돼 있어 채널만 연결하면 된다.",
"impact": "medium",
"effort": "low"
},
{
"title": "CLI quick 경로에 취소 토큰 전파 + NuGet 취약점 경고 재활성화",
"rationale": "App.RunQuickAsync(App.xaml.cs:96)가 토큰 없이 ConvertManyAsync를 호출해 컨텍스트 메뉴 대량 변환을 중단 불가하다. CancellationTokenSource를 QuickProgressWindow의 취소 버튼과 연결한다. 동시에 csproj의 NoWarn NU1901-1904(Everything2Everything.Core.csproj:11)를 제거해, 외부 미디어를 파싱하는 WebView2/OpenXML/Magick 의존성의 알려진 CVE가 빌드에서 드러나게 한다.",
"impact": "medium",
"effort": "low"
},
{
"title": "대용량 입력 스트리밍/페이지 단위 메모리 관리",
"rationale": "PDF 페이지 전체를 MemoryStream에 디코딩(PdfProvider.cs:79)하고 OCR이 파일을 3중 복사(OcrProvider.cs:148-159)하며 멀티프레임 GIF를 통째로 Coalesce(MagickProvider.cs:84)하는 구조는 영상/대용량 PDF에서 메모리 폭증을 부른다. 페이지/프레임 단위로 디코딩-인코딩-해제하는 스트리밍 루프와, OCR의 불필요한 ToArray() 복사 제거가 필요. 영상 코덱·PDF 압축 기능의 전제 조건.",
"impact": "medium",
"effort": "high"
},
{
"title": "외부 프로세스 입력 검증 강화 (LibreOffice 출력 파일명 충돌)",
"rationale": "세 Provider가 LibreOffice 출력 파일명을 GetFileNameWithoutExtension(sourcePath)+'.'+fmt로 예측(DocumentProvider.cs:272 등)하는데, 병렬화하거나 동명이파일을 같은 outDir로 변환하면 결과물이 서로 덮어쓴다. 인젝션은 ArgumentList로 막혀 있으나, 출력 파일명 격리(작업별 고유 폴더)가 병렬화 도입 시 필수 선결 조건이다.",
"impact": "low",
"effort": "low"
}
],
"keyFiles": [
{
"path": "src/Everything2Everything.Core/ConversionEngine.cs",
"role": "배치 오케스트레이션 핵심. 순차 for-loop(57), 취소 체크(59), 예외→ConvertResult 격리(117-124), CombineAsync의 Task.Run 래핑(186-200). 병렬화/진행모델 재설계의 진원지."
},
{
"path": "src/Everything2Everything.Core/Converters/DocumentProvider.cs",
"role": "외부 프로세스(LibreOffice) 호출 + 임시작업폴더 + 라우팅 분기의 대표. ArgumentList 안전 패턴(254-261), Kill on cancel(266-267), stderr 미수집(252), 타임아웃 부재."
},
{
"path": "src/Everything2Everything.Core/Converters/OcrProvider.cs",
"role": "메모리 3중 복사(148-159)·라이브러리 경계 취소 단절(RecognizeAsync ct 미전달, 164)·임시 PDF 페이지 정리(111-119)의 표본."
},
{
"path": "src/Everything2Everything.Core/Converters/HtmlProvider.cs",
"role": "STA 스레드+Dispatcher 격리(162-181), CDP base64 전량 메모리 적재(76-98, 252-280), 취소 등록(227)·자원 정리 finally(245-249). WebView2 COM 수명 관리의 모범이자 메모리 비스트리밍의 예."
},
{
"path": "src/Everything2Everything.App/Views/MainWindow.xaml.cs",
"role": "UI 측 취소(OnCancelProcessingClick 619)·_cts 수명(516/574)·실패결과 조용한 제거(543-564)·MessageBox 단일 에러채널(569). 에러 표면화/취소 UX 개선 지점."
},
{
"path": "src/Everything2Everything.App/App.xaml.cs",
"role": "전역 예외 안전망 3종 배선(122-154)과 CLI quick의 취소토큰 누락(96). 에러 처리 일관성/취소 완전성의 양면."
},
{
"path": "src/Everything2Everything.Core/Everything2Everything.Core.csproj",
"role": "NuGet 취약점 경고 전체 억제(NoWarn NU1901-1904, line 11). ResourceLimits 미설정. 보안/공급망 위생의 출발점."
},
{
"path": "src/Everything2Everything.Core/OutputPathHelper.cs",
"role": "충돌 해소·파일명 살균 순수 로직. 외부 의존성 없는 단위 테스트의 1순위 대상이자 병렬화 시 race 검토 지점."
}
]
}
]