virtual-insanity
← 뒤로

볼트 정리 Codex 실행 스펙

seedling 2026-03-27

볼트 정리 Codex 실행 스펙

Claude가 감사한 결과를 기반으로 Codex가 실행할 Python 스크립트 명세. 볼트 경로: ~/knowledge/ 실행 전 반드시 dry-run 옵션으로 먼저 확인할 것.


작업 1: MOC 중복 삭제 (35개)

삭제 확정 파일 목록

DELETE_MOC = [
    # 기업 MOC 중복 (내용 빈약하거나 잘못된 섹터)
    "300 지식망/319 경기소비재/MOC-BABA.md",
    "300 지식망/331 정보기술/MOC-BABA.md",
    "300 지식망/316 산업재/MOC-CATL.md",
    "300 지식망/331 정보기술/MOC-KAI.md",
    "300 지식망/316 산업재/MOC-LIG넥스원.md",
    "300 지식망/331 정보기술/MOC-TSLA.md",
    "300 지식망/331 정보기술/MOC-메리츠증권.md",
    "300 지식망/334 커뮤니케이션/MOC-텐센트.md",
    "300 지식망/331 정보기술/MOC-하이브.md",
    "300 지식망/331 정보기술/MOC-한화에어로스페이스.md",
    "300 지식망/319 경기소비재/MOC-현대차.md",
    # 주제 MOC 중복 (내용 빈약한 쪽)
    "300 지식망/MOC-경제성장-경기사이클.md",
    "300 지식망/391 MOC/MOC-기업-금융.md",
    "300 지식망/391 MOC/MOC-기업-반도체.md",
    "300 지식망/MOC-기업-산업재.md",
    "300 지식망/MOC-기업-에너지.md",
    "300 지식망/391 MOC/MOC-기업.md",
    "300 지식망/MOC-물가-물가지표.md",
    "300 지식망/391 MOC/MOC-물가인플레.md",
    "300 지식망/382 밸류에이션/MOC-밸류에이션-멀티플.md",
    "300 지식망/MOC-산업분석-해운.md",
    "300 지식망/391 MOC/MOC-석유화학.md",
    "300 지식망/391 MOC/MOC-원자재-산업금속.md",
    "300 지식망/310 에너지/MOC-원자재-에너지원자재.md",
    "300 지식망/391 MOC/MOC-원자재-에너지원자재.md",
    "300 지식망/MOC-인사이트.md",
    "300 지식망/MOC-전략.md",
    "300 지식망/MOC-정책-무역정책.md",
    "300 지식망/MOC-정책-지정학.md",
    "300 지식망/391 MOC/MOC-조선기술.md",
    "300 지식망/391 MOC/MOC-통화금리-금리-채권.md",
    "300 지식망/391 MOC/MOC-프로그래밍-Claude-LLM.md",
    "300 지식망/391 MOC/MOC-프로그래밍-OpenClaw.md",
    "300 지식망/MOC-프로그래밍-시스템운영.md",
    "300 지식망/370 기술/MOC-2차전지.md",
]

이동 권고 파일 (300 지식망 루트 / 200 아토믹 → 391 MOC)

MOVE_TO_391_MOC = [
    # 200 아토믹 내 MOC (비표준 위치)
    "200 아토믹/215 통화/금리/MOC-시장-ETF전략.md",
    "200 아토믹/215 통화/금리/MOC-시장.md",
    "200 아토믹/243 인사이트/MOC-시장-매크로.md",
    "200 아토믹/243 인사이트/MOC-시장-수급.md",
    # 300 지식망 루트 내 MOC (391로 이동)
    "300 지식망/MOC-경제성장-고용-소비.md",
    "300 지식망/MOC-기술-AI-컴퓨팅.md",
    "300 지식망/MOC-기술산업기술.md",
    "300 지식망/MOC-기술적분석-차트-지표.md",
    "300 지식망/MOC-기술적분석.md",
    "300 지식망/MOC-기업-바이오.md",
    "300 지식망/MOC-기업-중국-신흥.md",
    "300 지식망/MOC-기업-콘텐츠.md",
    "300 지식망/MOC-밸류에이션-수익성.md",
    "300 지식망/MOC-밸류에이션.md",
    "300 지식망/MOC-산업분석-반도체산업.md",
    "300 지식망/MOC-산업분석-석화.md",
    "300 지식망/MOC-산업분석.md",
    "300 지식망/MOC-수급펀더멘탈.md",
    "300 지식망/MOC-수급포지셔닝.md",
    "300 지식망/MOC-인사이트-프레임워크.md",
    "300 지식망/MOC-전략-주식전략.md",
    "300 지식망/MOC-정책지정학.md",
    "300 지식망/MOC-크레딧-채권시장.md",
    "300 지식망/MOC-크레딧-크레딧스프레드.md",
    "300 지식망/MOC-크레딧채권.md",
    "300 지식망/MOC-통화금리-FX-달러.md",
    "300 지식망/MOC-포지셔닝-수급동향.md",
]

작업 2: 수신함 자동 분류

TRASH 규칙 (이동 대상 → _trash/)

import re
from pathlib import Path

VAULT = Path.home() / "knowledge"

def is_trash(filepath: Path) -> tuple[bool, str]:
    """파일이 trash 대상인지 판단. (True, 이유) 반환."""
    rel = str(filepath.relative_to(VAULT))
    size = filepath.stat().st_size
    name = filepath.name

    # T2: 지식사랑방 비투자 토픽 폴더
    NON_INVEST_FOLDERS = ["134 fitness", "135 food", "136 travel", "137 culture", "139 food"]
    if "120 지식사랑방" in rel:
        for folder in NON_INVEST_FOLDERS:
            if folder in rel:
                return True, f"T2: 비투자 토픽 폴더 ({folder})"

    # T1: 지식사랑방 초소형 파일 (잡담)
    if "120 지식사랑방" in rel and size < 500 and name != "_INDEX.md":
        return True, "T1: 지식사랑방 500B 미만"

    # T3: 잡담 키워드 파일명
    JUNK_PATTERNS = [r"^ㄷㄷ", r"^ㅋㅋ", r"^ㅎㅎ", r"^와우", r"^어허",
                     r"중복.테스트", r"힘내", r"뽀이팅", r"인듯용"]
    stem = filepath.stem
    if "120 지식사랑방" in rel:
        for pat in JUNK_PATTERNS:
            if re.search(pat, stem):
                return True, f"T3: 잡담 키워드 ({stem})"

    # T4: 텔레그램 URL-only 파일 (크롤링 실패)
    if "118 텔레그램" in rel and re.search(r"\d{6}_\d+_https[a-z]", name) and size < 1500:
        return True, "T4: 텔레그램 URL-only 소형"

    # T7: 지식사랑방 URL-only
    if "120 지식사랑방" in rel and re.match(r"https?", stem) and size < 700:
        return True, "T7: 지식사랑방 URL-only"

    return False, ""

# T5: HTML 잔해 _ref 파일 (내용 확인 필요)
def is_html_junk_ref(filepath: Path) -> bool:
    if not filepath.name.endswith("_ref.md"):
        return False
    content = filepath.read_text(errors="ignore")
    junk_patterns = ["언론사 구독되었습니다", "메인 뉴스판에서", "댓글 정책", "이동 통신망을 이용하여"]
    body = content.split("---", 2)[-1] if "---" in content else content
    junk_count = sum(1 for p in junk_patterns if p in body)
    real_text = len([line for line in body.splitlines() if line.strip() and not line.startswith("  ")])
    return junk_count >= 2 and real_text < 10

이미지 분석 섹션 트리밍 (~362개 파일)

import re

def trim_image_analysis(content: str) -> str:
    """OG 이미지 분석 블록 제거. 파일 자체는 유지."""
    # <!-- og_*.jpg --> ~ <!-- media_analyzed --> 블록 제거
    pattern = r'<!-- og_[^\n]*\.(?:jpg|png|gif|webp)[^\n]* -->\n\*\*이미지 분석\*\*.*?<!-- media_analyzed -->\n?'
    return re.sub(pattern, '', content, flags=re.DOTALL)

# 적용 대상: 116 croned_data 내 이미지 분석 섹션 포함 파일

KEEP 보호 규칙 (삭제 금지)

def is_protected(filepath: Path) -> bool:
    name = filepath.name
    content_peek = filepath.read_text(errors="ignore")[:500]
    # K2: _daily.md
    if name == "_daily.md":
        return True
    # K3: popular-posts 배치 파일
    if re.match(r"popular-posts-\d{4}-\d{2}-\d{2}-\d{4}\.md", name):
        return True
    # K4: atomized_into 필드 있는 파일
    if "atomized_into" in content_peek:
        return True
    # K5: 타임폴리오 ETF
    if "타임폴리오_ETF" in name:
        return True
    # K6: split_reason 있는 분할 노트
    if "split_reason" in content_peek:
        return True
    return False

작업 3: 해시 중복 파일 삭제 (41개)

# 파일명 끝 _XXXXXX.md (6자리 hex) 패턴
# 같은 폴더에 원본(해시 없는 버전)이 있으면 해시 파일 삭제

import re
from pathlib import Path

def find_hash_duplicates(vault: Path):
    pattern = re.compile(r'^(.+)_([a-f0-9]{6})\.md$')
    to_delete = []
    for f in vault.rglob("*.md"):
        m = pattern.match(f.name)
        if m:
            original = f.parent / f"{m.group(1)}.md"
            if original.exists():
                to_delete.append(f)
    return to_delete

작업 4: 빈 폴더 삭제 (40개+)

def find_empty_dirs(vault: Path):
    empty = []
    for d in sorted(vault.rglob("*"), reverse=True):
        if d.is_dir() and not any(d.iterdir()) and "_trash" not in str(d):
            empty.append(d)
    return empty

스크립트 실행 구조

#!/usr/bin/env python3
"""
vault_cleanup.py
사용법:
  python3 vault_cleanup.py --dry-run   # 변경 없이 목록만 출력
  python3 vault_cleanup.py --execute   # 실제 실행 (_trash/로 이동 또는 삭제)
"""

import argparse, shutil, re
from pathlib import Path
from datetime import datetime

VAULT = Path.home() / "knowledge"
TRASH = VAULT / "_trash" / f"cleanup-{datetime.now().strftime('%Y%m%d')}"

def safe_trash(filepath: Path, dry_run: bool, reason: str):
    if dry_run:
        print(f"[TRASH] {filepath.relative_to(VAULT)} — {reason}")
        return
    dest = TRASH / filepath.relative_to(VAULT)
    dest.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(filepath), str(dest))
    print(f"이동: {filepath.name} → _trash/")

def safe_move(filepath: Path, dest_dir: Path, dry_run: bool):
    if dry_run:
        print(f"[MOVE] {filepath.relative_to(VAULT)} → {dest_dir.relative_to(VAULT)}/")
        return
    dest = dest_dir / filepath.name
    dest.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(filepath), str(dest))

# main() 에서 위 규칙들 순서대로 실행
# 1. MOC 삭제 (DELETE_MOC)
# 2. MOC 이동 (MOVE_TO_391_MOC)
# 3. 수신함 trash 규칙 (T1-T7)
# 4. 이미지 분석 트리밍
# 5. 해시 중복 삭제
# 6. 빈 폴더 삭제

예상 결과

작업 파일 수 방식
MOC 삭제 35개 _trash/ 이동
MOC 이동 27개 391 MOC/로 이동
수신함 trash ~270개 _trash/ 이동
이미지 분석 트리밍 ~362개 파일 내 섹션 제거
해시 중복 삭제 ~41개 _trash/ 이동
빈 폴더 삭제 ~40개 폴더 삭제

총 정리 효과: ~373개 파일 제거 + 362개 파일 경량화