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 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 ' , (: HWPPDFPNG), , ",
"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) 'HWPDOCX 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) + , (HWPPDFPNG) ' ' . 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 . HWPDOCX, ",
" //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 MemoryStreamToArray()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) = 1N .",
"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(mdhtmlmd) . 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 ."
}
]
}
]