Projects

NotionToBlog

GitHub ↗

Notion 일기 → Hugo 블로그 자동 발행 파이프라인

README.md

NotionToBlog

Notion 일기 DB의 오늘자 페이지를 한 명령으로 Hugo(GitHub Pages) 포스트로 발행하는 개인용 파이프라인. Self-dogfood 전용 — PyPI 배포 의도 없음, Windows + Python 3.14 단일 지원. (내부 패키지 식별자는 dayblog / dayblog_mcp.)

Highlights

  • Notion 일기 → Hugo + GH Pages 자동 발행 — 두 차례 후속 릴리스(v0.2.0 / v0.2.1)로 실사용 피드백을 반영한 실가동 도구
  • Claude Code의 Hook + Skill + MCP 3축을 의도적으로 한정 적용 (Subagent / Plugin / Scheduled task는 범위 외) — 깊이를 위해 너비를 포기한 설계
  • 124 tests · GitHub Actions CI · Notion API 2025-09-03 (data_sources.query) 마이그레이션 대응
  • 핵심 설계 결정과 트레이드오프는 docs/domain-notes.md에 한 페이지로 정리 — commit history 자체가 곧 개발 일지
┌─────────────────────────┐         ┌──────────────────────────┐
│  NotionToBlog (this)    │         │  Hugo site (external)    │
│  - src/dayblog/         │ writes  │  D:\vscodeprojects\blog  │
│  - src/dayblog_mcp/     │ ──────► │  - content/posts/<slug>/ │
│  - .claude/ hooks       │         │  - themes/PaperMod/      │
│                         │         │  - .git/hooks/pre-push   │
└─────────────────────────┘         └──────────────────────────┘
         ↑                                     ↑
  Claude Code harness                   hugo server + git push
  (Hook + Skill + MCP)                   (GH Pages 배포)

요구 사항

  • Python 3.14
  • Windows (서브프로세스 · 경로 규약이 Windows 전제)
  • Hugo Extended (PaperMod SCSS)
  • Notion Integration 토큰 + 대상 DB ID + 통합이 DB에 초대됨
  • 외부 Hugo 사이트 레포 (예: D:\vscodeprojects\blog) — PaperMod 테마가 sub-module로 등록돼 있으면 git submodule update --init --recursive 필수

설치

pip install -e .[mcp,dev]

[mcp] extra = fastmcp, notion-client, httpx (Notion 연동). [dev] = pytest, ruff. 최소 실행(네트워크 없는 Hugo 툴링만)은 core deps(pyyaml, python-dotenv)로 충분.

설정

레포 루트에 .env 작성:

NOTION_TOKEN=ntn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
NOTION_DATABASE_ID=<32-hex, dashes optional>
HUGO_SITE_ROOT=D:\vscodeprojects\blog

Notion DB 스키마 요구사항 (docs/domain-notes.md §4):

속성 타입 필수
Title (또는 Name) title
Date date
Status select (Draft / Ready / Published)
Tags multi_select
Category select
Summary rich_text

/publish-todayStatus == Ready 필터만 잡습니다.

실행

CLI

python -X utf8 -m dayblog publish-today [--page-id <ID>] [--date YYYY-MM-DD]
python -X utf8 -m dayblog new-post --title "<제목>" [--date YYYY-MM-DD]
python -X utf8 -m dayblog list-drafts
python -X utf8 -m dayblog validate <path>
python -X utf8 -m dayblog install-pre-push    # Hugo 레포에 훅 설치

Claude Code 슬래시 커맨드

  • /today — 오늘자 Ready 페이지 목록 (진단)
  • /publish-today [page_id?] — Notion → Hugo 번들 발행
  • /post-new [date?] <title> — 수동 드래프트 스케폴드 (Notion 우회)
  • /draft-list / /publish-queuedraft: true 포스트 나열

MCP 툴 (.mcp.json이 자동 등록)

  • notion_list_pages(date?) — Ready 페이지 요약 목록
  • notion_get_page(page_id) — 원본 metadata + top-level blocks (진단용)
  • notion_render_markdown(page_id) — Markdown + 이미지 매니페스트 + 경고

글 작성·수정 플로우

새 글 발행

  1. Notion dayblog-journal DB에서 오늘자 페이지 작성 (Date / Title / 일기 본문)
  2. 페이지 맨 아래에 Heading 1 블로그 추가 → 그 아래 블로그용 내용 작성 (이 마커가 없으면 발행이 거부됨, domain-notes §9)
  3. StatusReady 로 변경
  4. python -X utf8 -m dayblog publish-today (또는 Claude /publish-today) — created 또는 updated 출력
  5. hugo server → 로컬 프리뷰 확인 (-D 불필요, baseURL이 서브패스면 http://localhost:1313/blog/)
  6. git add -A && git commit -m "post: <slug>" && git push — pre-push 훅이 손편집 draft 검사 후 GH Pages 자동 배포

Notion Status == Ready가 이미 발행 게이트 역할을 하므로 publish-today는 항상 draft: false로 직행 — 수동 플립 단계 없음.

발행된 글 수정

  1. Notion에서 본문 수정 → last_edited_time 자동 갱신
  2. python -X utf8 -m dayblog publish-today → idempotency가 updated로 감지, 번들 덮어씀
  3. git diff 확인 → commit + push

삭제

  • Notion StatusDraft: 이후 publish-today 대상에서 빠짐 (기존 번들은 손대지 않음)
  • 블로그에서도 지우려면 Hugo 레포의 해당 번들 디렉토리를 수동 삭제 후 commit + push

트러블슈팅: 빈 포스트가 나옴

  • 결과가 skipped-no-marker 면 Notion 페이지 본문에 top-level Heading 1 블로그 마커가 없음. toggle/callout 안의 H1은 인식 안 함 — 페이지 최상위 형제로 둬야 함.
  • Title/Date/Status property 이름이 정확히 매치되는지 (Status == Ready).

Idempotency

/publish-today를 같은 날짜로 여러 번 돌려도 안전합니다 (domain-notes §2 #1):

  • 기존 번들의 source_notion_id가 같고 lastmod ≥ Notion.last_edited_timeskipped
  • 더 오래됐으면 → updated (덮어쓰기)
  • 다른 페이지가 같은 날짜 slug를 점유 중이면 → -2/-3/… 로 자동 증분

Draft 보호 (Double guard)

NotionToBlog는 draft: true 포스트가 실수로 GH Pages에 올라가는 걸 막기 위해 두 훅을 동시에 설치합니다. (Notion publish-today는 이미 draft: false로 직행하므로 자동 발행 흐름에서는 차단되지 않습니다 — 훅은 /post-new로 만든 수동 드래프트 + 손편집으로 draft:true가 된 포스트 보호 용도.)

1. .git/hooks/pre-push (터미널 git push 커버)

Hugo 레포에서 한 번:

cd D:\vscodeprojects\blog
python -X utf8 -m dayblog install-pre-push

설치 후 git push 시 pushed 범위 안에 draft 포스트가 있으면 exit 1로 차단하고 파일 경로를 stderr에 나열합니다.

2. Claude Code PreToolUse (Claude가 내는 Bash 커버)

.claude/settings.json에 등록돼 있음. Claude가 git push Bash 툴을 호출할 때 JSON deny로 차단합니다. 중요: 이 훅은 HUGO_SITE_ROOT를 읽어서 그 레포를 스캔합니다 — .env에 제대로 세팅돼 있어야 작동.

왜 둘 다인가

Claude Code PreToolUse 훅은 구조적으로 "Claude가 Bash 툴로 실행하는" push만 가로챕니다. 사용자가 직접 터미널에서 git push하면 이 훅은 호출되지 않습니다. 그래서 git의 네이티브 pre-push도 필수. 두 훅이 동일한 Python 모듈(dayblog.hooks.pre_push_guard)을 호출하므로 로직은 DRY.

훅 우회 (의도적 push)

draft를 유지한 채로도 push해야 할 (매우 드문) 경우:

  • 터미널: git push --no-verify
  • Claude: 세션에서 훅 비활성화 (권장하지 않음)

로컬 렌더 확인

cd D:\vscodeprojects\blog
hugo server -D
# baseURL 서브패스가 있으면 http://localhost:1313/blog/

-D는 drafts 포함. 플립 전에 로컬에서 한 번 보고 draft: false로 바꿔 push.

범위 외

  • PyPI 배포 (self-dogfood 전제)
  • 과거 Notion 일기 일괄 마이그레이션
  • Notion DB 역방향 동기화 (블로그 → Notion)
  • Windows 외 OS, Python 3.14 외 버전
  • Subagent / Plugin / Scheduled task / Status line (일부러 깊이 제한)

테스트

pytest              # 111+ tests
pytest -k smoke
ruff check src tests

문서

라이선스

MIT (Private :: Do Not Upload — PyPI 배포 금지).

OSSMate

GitHub ↗

OSS 메인테이너의 반복 업무를 Claude Code 플러그인과 독립 CLI로 자동화하는 프로젝트입니다.

README.md

Ossmate

🇰🇷 한국어 · 🇬🇧 English

CI PyPI Claude Code Plugin Python License: MIT

Claude 기반 오픈소스 메인테이너 보조 도구.

Ossmate는 OSS 메인테이너의 반복 업무 — PR 트리아지, 이슈 분류, 릴리스 노트 작성, 의존성 감사, stale 이슈 정리, 컨트리뷰터 온보딩 — 을 Claude Code 플러그인과 독립 CLI로 자동화합니다.

두 가지 목적으로 만들어졌습니다:

  1. 실용 도구 — 이슈 큐에 빠져 죽는 솔로 메인테이너를 위한 진짜 도구.
  2. 레퍼런스 구현체 — Claude Code의 모든 확장 표면(Skills, Subagents, Hooks, MCP, Plugins, Agent SDK, Cron, Status line, Output styles, Memory, Settings, Keybindings)을 하나의 일관된 제품으로 결합한 사례.

구축된 표면

표면 위치 상태
Skills (슬래시 명령) .claude/commands/ [x] Phase 5 (8/8)
Subagents .claude/agents/ [x] Phase 5 (6개 — haiku/sonnet/opus 매칭)
Hooks .claude/hooks/ [x] Phase 3 (5개 이벤트, 21개 테스트)
MCP 서버 mcp/ossmate_mcp/ [x] Phase 4 (11개 도구, 3개 템플릿)
플러그인 패키징 .claude-plugin/ [x] Phase 6 (manifest + 자체 마켓플레이스)
Claude Agent SDK CLI cli/ossmate/ [x] Phase 7 (Typer + 8개 서브커맨드, dry-run 모드)
Status line .claude/statusline.sh [x] Phase 1
Output styles .claude/output-styles/ [x] Phase 1
Scheduled triggers scheduled/ [x] Phase 8 (3개 cron 잡, off-minute 분산)
Memory templates .claude/CLAUDE.md [x] Phase 0
Settings & permissions .claude/settings.json [x] Phase 0
Keybindings .claude/keybindings.json.example [x] Phase 1
CI / Release .github/workflows/ [x] Phase 9 (3-OS × 3-Python 매트릭스, OIDC PyPI 발행)

빠른 시작

세 가지 사용 방법이 있습니다. 본인 환경에 맞는 것을 고르세요.

A. Claude Code 플러그인 (권장)

claude plugin marketplace add https://raw.githubusercontent.com/sunjin12/ossmate/main/.claude-plugin/marketplace.json
claude plugin install ossmate@ossmate

설치 후 어느 repo에서나:

/triage-pr 1234
/release-notes v1.4.0
/stale-sweep --days 60

플러그인은 자체 네임스페이스를 가지므로 /ossmate:triage-pr 1234 형식도 작동합니다 — 다른 플러그인과 명령 이름이 충돌할 때 네임스페이스 형식을 쓰세요.

B. 독립 CLI

pipx install ossmate
ossmate triage-pr 1234
ossmate release-notes v1.4.0
ossmate triage-pr 1234 --dry-run    # 렌더링된 prompt + ClaudeAgentOptions 출력

CLI는 플러그인이 사용하는 동일한 .claude/commands/*.md 스킬 본문을 로드합니다 — 스킬을 한 번 작성하면 슬래시 명령과 CLI 서브커맨드가 자동으로 함께 생깁니다.

설치 후 첫 명령이 실패한다면 ossmate doctor로 환경(Python, gh CLI 인증, MCP 서버, .claude/·.ossmate/ 디렉터리)을 먼저 진단하세요. --json 플래그로 CI에서도 사용 가능합니다.

C. 소스에서 (개발용)

git clone https://github.com/sunjin12/ossmate.git
cd ossmate
bash scripts/dev_link.sh        # mcp + cli editable 설치, --check 자동 실행
# Windows:
# powershell -ExecutionPolicy Bypass -File scripts/dev_link.ps1
pytest -q                       # 161개 hermetic 테스트, ~5초

아키텍처

flowchart LR
    User([Maintainer])
    User -->|slash commands| CC[Claude Code]
    User -->|terminal / CI| CLI[ossmate CLI]
    CC --> Skills[Skills]
    CC --> Hooks[Hooks]
    Skills --> Subagents
    Subagents --> MCP[ossmate MCP server]
    CLI --> SDK[Claude Agent SDK]
    SDK --> MCP
    MCP --> GH[(GitHub API)]
    MCP --> Repo[(Local repo)]
    MCP --> Adv[(OSV advisories)]

같은 MCP 서버가 플러그인과 독립 CLI 양쪽을 모두 지원합니다 — 도구를 한 번 작성하면 어디서나 사용 가능.


왜 모든 표면을 사용했나?

12개 하네스 확장 각각이 OSS 메인테이너 도메인의 특정 마찰을 제거합니다. 단순 데모 매핑이 아닙니다 — 아래 표는 각 표면이 없을 때 어떤 마찰이 생기는지로 정당화합니다.

표면 없으면 발생할 마찰
Skills (슬래시 명령) 반복 워크플로(triage / release notes / stale sweep)를 매번 프롬프트 처음부터 작성
Subagents 모든 업무를 단일 모델로 — bulk 분류에 Opus, 보안 감사에 Haiku 같은 모델 미스매치 비용
Hooks git push origin main 실수 1건이 수 시간 복구 비용. 인간 의지력에만 의존
MCP 서버 slash / CLI / 다른 AI 클라이언트가 gh · OSV · lockfile 파싱을 각자 재구현
플러그인 패키징 온보딩이 pip install + .claude/ 복사 + settings 편집 다단계
Agent SDK CLI CI · 원격 shell · non-Claude-Code 환경에서 동일 워크플로 재구성 필요
Status line 현재 저장소의 PR 수 · stale 수가 매번 브라우저 탭으로만 확인
Output styles CHANGELOG의 Keep-a-Changelog 포맷 · 리뷰 tone을 매번 수동 교정
Scheduled triggers 의지력에 의존한 일간 체크 — 며칠 skip되면 악순환
Memory templates persona · 브랜치 보호 정책 · 커밋 컨벤션을 매 세션 다시 설명
Settings & permissions destructive gh 차단을 매번 훅으로만 방어 — 허용목록 baseline 없음
Keybindings 파워유저 전용 gesture 부재 (옵션 — 가장 낮은 가치)

각 표면의 빌드 순서와 동기는 이 저장소의 PR 이력과 phase-0 ~ phase-9 태그를 참조하세요.


Self-dogfooding

이 저장소의 CHANGELOG는 Ossmate 자신이 생성하고, 스케줄된 트리거가 매일 이 repo에 다이제스트를 돌리며, PreToolUse 훅이 실수로 main에 force-push 하는 것을 막습니다. CHANGELOG.md를 참조하세요.


릴리스 방법

두 PyPI 프로젝트(ossmate-mcp, ossmate)는 함께 출시됩니다 — 버전 올리고, 태그 달고, push만 하면 GitHub Actions가 나머지를 처리합니다:

python scripts/bump_version.py 0.2.0   # 두 pyproject + plugin.json + marketplace.json 동시 갱신
python scripts/bump_version.py --check # invariant: 5곳 모두 일치 확인
git commit -am "chore(release): v0.2.0"
git tag v0.2.0 && git push --tags      # PreToolUse 훅이 명시적 승인 없이는 차단

릴리스 워크플로우는 태그 버전과 pyproject.toml 버전이 다르면 발행을 거부합니다. PyPI 업로드는 OIDC 신뢰 발행 방식 — 저장소 시크릿에 API 토큰이 없습니다.


개발 현황

단계별로 빌드되었습니다 — 계획은 docs/project_phases.md에 있습니다. 각 단계는 태그(phase-0, phase-1, …)로 마킹되어 있어 프로젝트의 진화 과정을 따라가볼 수 있습니다.

v0.1.0 (2026-04-19) — 첫 공개 릴리스. 모든 12개 표면 작동, PyPI 발행 완료.


About / 프로젝트 회고

이 프로젝트가 어떤 문제를 해결하려 했는지, 어떻게 구현했는지, 구현 과정에서 내린 결정, 자체 평가를 정리한 회고는 docs/portfolio.md에 있습니다.

이 프로젝트는 클로드 코드의 도움을 받아 작성되었습니다.

라이선스

MIT — LICENSE 참조.

SCP World

GitHub ↗

SCP Foundation 위키 콘텐츠를 RAG로 검색하고, 재단 페르소나(연구원/요원/SCP-079)로 답변하는 챗봇 데모. 100% 서버리스 (Cloud Run + Firebase Hosting + Firestore) 로 운영됩니다.

README.md

SCP World

SCP Foundation 위키 콘텐츠를 RAG로 검색하고, 재단 페르소나(연구원/요원/SCP-079)로 답변하는 챗봇 데모. 100% 서버리스 (Cloud Run + Firebase Hosting + Firestore) 로 운영됩니다.


Overview: 프로젝트 개요

기획 배경 및 목표

세계관을 탐구할 때, 단순히 위키를 읽는 것이 아니라 하나의 페르소나를 가진 캐릭터와 대화하며 알아가면 재밌을 것 같다고 생각했습니다. 방대한 자료와 오픈된 라이선스(CC BY-SA 3.0)를 가진 SCP 위키를 기반으로, 3가지 캐릭터를 통해 SCP 세계관에 대해 탐구할 수 있는 챗봇을 만들었습니다.

핵심 기능

기능 설명
페르소나 선택 연구원(Dr. [REDACTED]), 요원(Agent [REDACTED]), SCP-079(Old AI) 중 선택
RAG 기반 대화 SCP 위키 문서를 벡터 검색하여 근거 있는 답변 생성
SSE 스트리밍 토큰 단위 실시간 스트리밍으로 자연스러운 대화 경험
페르소나별 격리 캐릭터마다 독립된 대화 세션 유지
Google 로그인 OAuth 2.0 기반 인증, ID Token으로 모든 API 보호
출처 표시 답변에 사용된 SCP 위키 원문 URL 제공

기술 스택

레이어 기술
Frontend Flutter Web, Riverpod, go_router, Google Sign-In v7
Backend API FastAPI, Uvicorn, Python 3.11
LLM Serving vLLM (Qwen2.5-7B-Instruct), NVIDIA L4 GPU
Embedding BAAI/bge-m3 (1024차원)
Vector DB Firestore Native Vector Search (find_nearest, COSINE)
Infra Cloud Run (Scale-to-Zero), Firebase Hosting, Firestore
Auth Google OAuth 2.0, ID Token 검증

시스템 상세 설명


System Architecture

시스템 구성도

시스템 구성도

데이터 흐름도

데이터 수집 파이프라인 (오프라인)

데이터 수집 파이프라인

실시간 질의 파이프라인 (온라인)

실시간 질의 파이프라인


라이선스

두 라이선스는 서로 다른 대상에 적용됩니다. 본 저장소의 코드를 재사용할 때는 MIT, RAG로 제공되는 SCP 위키 텍스트를 재배포할 때는 CC-BY-SA 3.0 조건을 따라야 합니다.

안내

클로드 코드를 활용하여 작성되었습니다

RAG Chat Project

GitHub ↗

Retrieval-Augmented Generation 기반의 AI 채팅 애플리케이션 — 사용자가 직접 AI 페르소나를 생성하고 문서를 업로드하여 질의응답(Q&A)을 할 수 있는 풀스택 프로젝트

README.md

RAG Chat

사용자가 AI 페르소나를 생성하고, 문서(PDF/텍스트/오디오)를 업로드한 뒤, 해당 문서를 기반으로 질의응답(Q&A)을 할 수 있는 RAG 채팅 애플리케이션입니다.


Overview: 프로젝트 개요

기획 배경 및 목표

사용자가 직접 업로드한 문서들을 바탕으로 특색있는 페르소나를 지닌 챗봇과 대화할 수 있는 RAG 파이프라인을 구축했습니다. 단일 봇이 아닌 페르소나 단위로 문서 컨텍스트를 격리하여, 용도별 전문 AI를 만들 수 있는 풀스택 프로젝트입니다.

핵심 기능

기능 설명
페르소나 생성 사용자가 직접 AI 페르소나를 생성하고 문서 컨텍스트를 격리
RAG 기반 대화 업로드된 문서를 벡터 검색하여 근거 있는 답변 생성
SSE 스트리밍 토큰 단위 실시간 스트리밍으로 자연스러운 대화 경험
다중 세션 관리 페르소나마다 독립된 대화 세션을 여러 개 유지
문서 업로드 PDF, TXT, MD, CSV, 오디오 파일 지원 (오디오는 STT 변환)
Google 로그인 OAuth 2.0 기반 인증, JWT로 모든 API 보호
품질 모니터링 RAG 검색 유사도를 자동 기록하고 리포트로 시각화

기술 스택

레이어 기술
Frontend Flutter Desktop (Windows), Provider, Dio, Google Sign-In
Backend API FastAPI, Uvicorn, SQLAlchemy, Pydantic, LangChain
LLM Ollama (qwen3:8b), NVIDIA GPU 가속
Embedding BAAI/bge-m3 (1024차원), sentence-transformers
Vector DB Qdrant (COSINE similarity)
Database PostgreSQL 15 (대화 기록), Redis 7 (캐시)
Auth Google OAuth 2.0, JWT (HS256)
Infra Docker Compose, NVIDIA Container Toolkit, Nginx (프로덕션)

시스템 상세 설명


System Architecture

시스템 구성도

시스템 구성도

데이터 흐름도

문서 업로드 파이프라인

문서 업로드 파이프라인

실시간 질의 파이프라인

실시간 질의 파이프라인


앱 화면

RAG Chat 대화 화면


안내

이 프로젝트는 학습 및 개인 프로젝트 목적으로 작성되었습니다. 클로드 코드를 활용하여 작성되었습니다.