Skip to content

Architecture

ClarusIubar edited this page Aug 17, 2026 · 5 revisions

Architecture

한국어 | English

common/                     # 벤더들이 공유하는 로직
├── markdown_safety.py       # 코드펜스 안전장치
├── text.py                  # first_sentence / yaml_quote / sanitize_filename / format_callout
├── session_markdown.py      # frontmatter + callout 마크다운 조립, content_hash 계산/추출,
│                             # turn 메타데이터 HTML 주석 삽입
├── attachment_cache.py      # 첨부파일 리졸버 공통 뼈대 (캐싱, dry-run 복사, 집계)
├── attachment_types.py      # 첨부파일 확장자 분류 (임베드 가능 여부 등, 벤더 공통)
├── zip_extract.py           # data/<vendor>/의 *.zip을 그 자리에 압축 해제 (zip slip 방어 포함)
├── fs_discovery.py          # __MACOSX 등 압축 도구 쓰레기 경로 필터링, 후보 모호성 처리
├── upsert.py                 # content_hash 비교 기반 upsert 쓰기 (result/용)
├── publish.py                 # result/ → 실제 vault 미러링 (--publish용, upsert 재사용,
│                              #  result_dir 하위 폴더까지 재귀 미러링)
└── config.py                   # config.json 로더 (없으면 기본값으로 생성)
vendors/
├── base.py                # 벤더 모듈 인터페이스 계약(Protocol) + 런타임 검증 + 자동 탐색
├── chatgpt.py              # conversations*.json 트리 파싱 + .dat 첨부파일 복원
├── gemini.py                # "내 활동.html" 블록 파싱 + 로컬 첨부파일 매칭
└── claude.py                # conversations.json(일반 대화) + design_chats/*.json(프로젝트
                            #  소속 에이전틱 대화, 별도 스키마) 파싱
run.py                     # CLI: config 로딩 + 경로 우선순위 해석 + 벤더 실행 + 발행
config.example.json        # config.json 구조 예시 (실제 config.json은 .gitignore 대상)
tests/                      # pytest — common/ 순수 함수 + 벤더 파싱 로직(트리 브랜치 선택,
                            # KST 파싱 등) + config/publish 유닛 테스트

설계 원칙

  • 공통 로직은 common/으로, 벤더별 파싱은 vendors/로. ChatGPT/Gemini/Claude의 원본 export 형식은 완전히 다르지만, 마크다운 조립·첨부파일 처리·upsert·config 로딩은 벤더 무관한 순수 로직이라 한 곳에서 공유합니다. Claude는 한 걸음 더 나아가 벤더 내부에서도 스키마가 갈립니다 — 프로젝트에 안 묶인 일반 대화(conversations.json)와 프로젝트 소속 에이전틱 대화(design_chats/*.json)가 필드 이름부터 다른 별개 스키마라, vendors/claude.py 하나 안에서 로더 두 벌을 두고 공통 turn 모델 ({role, text, time_str})로 합류시킵니다.
  • registry(dispatch table) 패턴. vendors/chatgpt.py의 PART_RENDERERS, vendors/gemini.py의 TAG_RENDERERS는 태그/콘텐츠 타입 → 렌더 함수를 매핑하는 dict입니다. if/elif 체인 대신 이 방식을 쓴 이유는 각 케이스가 상태 없이 독립적으로 처리 가능한 lookup-and-dispatch 형태이기 때문입니다. 반대로 gemini.py의 _parse_block(prompt → post_marker → response 상태 전이)처럼 진짜 순차적 상태 머신인 부분은 의도적으로 if/elif로 남겨뒀습니다 — registry 패턴을 일괄 적용하지 않고 각 코드의 실제 형태에 맞춰 골랐습니다. vendors/claude.py는 두 스키마마다 별도 레지스트리(STANDALONE_BLOCK_RENDERERS/PROJECT_BLOCK_RENDERERS)를 두는데, 이 덕분에 Anthropic이 새 콘텐츠 블록 타입을 추가해도 레지스트리에 없는 타입은 조용히 스킵될 뿐 파이프라인이 죽지 않습니다.
  • content-addressable upsert. session_id 존재 여부만으로 판단하지 않고, 렌더링된 본문의 content_hash를 비교해서 실제 변경 여부를 판정합니다 (자세한 내용은 Output Format).
  • zip-slip / 경로 순회 방어. common/zip_extract.py의 _is_safe_member()가 압축 해제 시 상위 디렉터리 이탈 경로를 차단하고, _is_junk_member()가 __MACOSX 등 압축 도구 쓰레기 파일을 걸러냅니다.
  • CLI > config > 기본값 우선순위, lazy config 로딩. run.py는 CONFIG = None을 모듈 레벨에 두고 main() 안에서만 load_config()를 호출합니다 — 이렇게 해야 import run만 해도(예: 테스트 수집 시) 실제 config.json이 부수효과로 생성되는 일을 막을 수 있습니다.

관련 문서: Development

Clone this wiki locally