볼트 정리 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개 파일 경량화