{"id":50324983,"url":"https://github.com/pathcosmos/sincenety","last_synced_at":"2026-05-29T05:04:28.202Z","repository":{"id":349744725,"uuid":"1203680288","full_name":"pathcosmos/sincenety","owner":"pathcosmos","description":"Claude Code 작업 갈무리 도구 — 세션 자동 분석, 토큰 추적, 이메일 리포트","archived":false,"fork":false,"pushed_at":"2026-04-16T08:10:10.000Z","size":422,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-16T08:16:20.080Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pathcosmos.png","metadata":{"files":{"readme":"README.ko.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-07T09:10:49.000Z","updated_at":"2026-04-16T08:10:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pathcosmos/sincenety","commit_stats":null,"previous_names":["pathcosmos/sincenety"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pathcosmos/sincenety","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Fsincenety","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Fsincenety/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Fsincenety/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Fsincenety/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pathcosmos","download_url":"https://codeload.github.com/pathcosmos/sincenety/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pathcosmos%2Fsincenety/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33637486,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-29T05:04:14.317Z","updated_at":"2026-05-29T05:04:28.186Z","avatar_url":"https://github.com/pathcosmos.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sincenety\n\n\u003e **[English Documentation (README.md)](./README.md)** | **[샘플 리포트](https://pathcosmos.github.io/sincenety/sample-report.html)** | **[CLI 리포트 (Workers AI)](https://pathcosmos.github.io/sincenety/sample-report-cli.html)**\n\n**Claude Code 작업 갈무리 도구** — 3단계 파이프라인으로 Claude Code 작업 이력을 자동 수집, 요약, 보고합니다.\n\nstart/stop 없이, 실행 시점에 소급하여 모든 작업을 자동 정리합니다.\n\n```\n$ sincenety\n\n  ☁️  D1 sync complete\n  ☁️  D1 sync complete\n  ✅ sincenety complete — 1 sent, 0 skipped\n\n$ sincenety air\n\n  📋 air complete\n     Date range: 3 days (backfill 2 days)\n     Total sessions: 12\n     Changed dates: 2\n     Changed: 2026-04-06, 2026-04-07\n\n$ sincenety circle\n\n  📋 circle complete\n     Date range: 3 days\n     Total sessions: 12\n     Changed dates: 2\n     Finalized: 2026-04-06\n     Needs summary: 2026-04-07\n```\n\n\u003e **참고**: v0.7.0부터 CLI 출력이 모두 영문으로 변경되었습니다.\n\n---\n\n## 핵심 기능\n\n### 기본 명령: 전체 파이프라인\n\n**v0.7.0** — `sincenety`를 인자 없이 실행하면 **air → circle → out** 전체 파이프라인이 자동으로 실행됩니다. D1 또는 이메일이 미설정이면 help + 설정 안내를 표시합니다.\n\n### 3단계 파이프라인: air → circle → out\n\n개별 단계로도 실행 가능합니다:\n\n1. **`sincenety air`** (환기) — 기록 수집/저장\n   - 날짜별 그룹핑 (자정 경계, startedAt 기준)\n   - 자동 백필: checkpoint 기반, 빈 날짜도 수집\n   - 변경 감지: data hash로 미변경 스킵\n   - 빈 날짜 기록 (세션 없어도 기록 생성)\n   - `--json` 옵션으로 날짜별 JSON 출력\n\n2. **`sincenety circle`** (순환 정화) — LLM 요약\n   - 내부적으로 `air` 자동 실행\n   - `--json`: 요약 필요 데이터 출력 (SKILL.md 연동)\n   - `--save`: stdin JSON → daily_reports 저장\n   - `--type daily|weekly|monthly`\n   - 자동 완료 처리: 자정→전날확정, 월요일→전주확정, 1일→전월확정\n   - 변경 감지: data hash 비교로 토큰 절약\n   - 휴가일에 [vacation] 라벨 자동 부여\n   - **프로젝트 단위 세션 통합 요약**: 같은 `projectName`의 모든 세션을 개별 요약 후 프로젝트별 통합 재요약 — 중복 제거 및 리포트 품질 향상\n\n3. **`sincenety out`** — 스마트 이메일 발신\n   - `out`: 일일보고 항상 발송, 금요일에 +주간보고, 월말에 +월간보고\n   - 미발송 캐치업: 금요일 놓치면 → 월요일에 주간보고 자동 발송\n   - 4가지 provider: Gmail MCP / Resend / Gmail SMTP / Custom SMTP\n   - `outd` / `outw` / `outm`: 일일 / 주간 / 월간 강제 발신\n   - `--preview`, `--render-only`, `--history`\n\n### CLI 명령어\n\n| 명령어 | 설명 |\n|--------|------|\n| `sincenety` | **전체 파이프라인** — air → circle → out 한 번에 실행 |\n| `sincenety air` | 환기 -- 날짜별 자동 백필 수집 |\n| `sincenety circle` | 순환 정화 -- LLM 요약 (--json/--save/--type) |\n| `sincenety out` | 스마트 발신 (요일+캐치업 자동) |\n| `sincenety outd` | 강제 일일 발신 |\n| `sincenety outw` | 강제 주간 발신 |\n| `sincenety outm` | 강제 월간 발신 |\n| `sincenety sync` | D1 중앙 동기화 |\n| `sincenety config` | 설정 (--setup, --vacation, --d1-*) |\n\n### 소급 갈무리\n\n별도 기록 행위 없이, `sincenety` 실행 시 `~/.claude/` 데이터를 분석하여 프로젝트별/세션별 작업 내용을 자동 재구성합니다.\n\n- **대화 턴 분석** — 사용자 입력 + 어시스턴트 응답 쌍(`conversationTurns`)을 함께 수집하여 정밀한 작업 내용 파악\n- **세션 JSONL 파싱** — `~/.claude/projects/[project]/[sessionId].jsonl`에서 토큰 사용량, 모델명, 정밀 타임스탬프, 대화 턴 추출\n- **checkpoint 기반 백필** — 마지막 checkpoint 이후 자동 수집; 첫 실행 시 90일 백필\n\n### 풍부한 작업 기록\n\n| 항목 | 설명 |\n|------|------|\n| 작업 타이틀 | 첫 사용자 메시지에서 자동 추출 |\n| 작업 설명 | 주요 사용자 메시지 3~5개 연결 |\n| 토큰 사용량 | 입력/출력/캐시 토큰 메시지별 합산 |\n| 작업 시간 | 첫 메시지 ~ 마지막 메시지 정밀 측정 |\n| 사용 모델 | assistant 응답에서 모델명 추출 |\n| 카테고리 | 프로젝트 경로 기반 자동 분류 |\n\n### AI 요약 엔진\n\n통합 AI provider 시스템 — **`ai_provider` 설정이 모든 환경(CLI, cron, Claude Code)에서 존중됩니다**:\n\n| `ai_provider` | `circle` 자동 요약 | `gatherer` 요약 | 사용 시나리오 |\n|----------------|-------------------|-----------------|-------------|\n| `cloudflare` | Workers AI (Qwen3-30B) → 실패 시 휴리스틱 | Workers AI | CLI / cron |\n| `anthropic` | 스킵 (자동 요약 없음) | Claude API (Haiku) | API 키 보유 시 |\n| `claude-code` | 스킵 (SKILL.md가 직접 처리) | 휴리스틱 | Claude Code `/sincenety` |\n| `auto` (기본값) | 자동 감지: cloudflare만 해당 | 자동 감지 | 초기 설정 |\n\n```bash\n# AI provider 설정 (모든 환경에서의 동작 제어)\nsincenety config --ai-provider cloudflare   # Workers AI 사용\nsincenety config --ai-provider anthropic    # Claude API 사용\nsincenety config --ai-provider claude-code  # Claude Code 직접 요약 (SKILL.md)\nsincenety config --ai-provider auto         # 자동 감지 (기본값)\n```\n\n- **Cloudflare Workers AI (Qwen3-30B)** 한국어 텍스트 요약 특화\n- D1 토큰만 있으면 자동 활성화 — 별도 API 키 불필요\n- `circle` 실행 시 `ai_provider`가 `cloudflare`일 때 자동 요약: 세션별 topic/outcome/flow/significance + 일일 overview\n- `circle --json --summarize`: Workers AI 요약을 JSON에 포함 (`ai_provider = cloudflare` 필요)\n- 무료 tier: 10,000 neurons/일 (개인 사용 충분, 하루 ~300회 요약 가능)\n- **휴리스틱 fallback**: Workers AI 개별 세션 호출 실패 시 휴리스틱 요약으로 대체 (데이터 손실 없음)\n\n### 이메일 AI 요약 통합\n\n이메일 보고서에 `daily_reports`의 AI 요약이 포함됩니다:\n- **overview 섹션**: 이메일 최상단에 하루 종합 요약\n- **세션별 매핑**: `daily_reports` wrapUp 데이터가 각 세션의 topic/outcome/flow/significance에 매핑\n- **Gmail 102KB 클립 방지**: 세션당 actions를 최대 5건으로 제한, 텍스트 길이 최적화\n\n### 필수 설정 (Mandatory)\n\nsincenety는 모든 커맨드 실행 전 **두 가지 설정**을 필수로 요구합니다:\n\n1. **D1 클라우드 동기화** — Cloudflare API 토큰 (Workers AI + 클라우드 동기화 활성화)\n2. **이메일 발송** — SMTP 또는 Resend (보고서 이메일 발송)\n\n```bash\n# 1단계: D1 토큰 (계정 자동 감지, DB 생성, Workers AI 활성화)\nsincenety config --d1-token \u003cAPI_TOKEN\u003e\n\n# 2단계: 이메일 설정 (대화형 위저드)\nsincenety config --setup\n# → Gmail 앱 비밀번호: https://myaccount.google.com/apppasswords\n```\n\n미설정 시 `config` 외 모든 커맨드가 차단됩니다.\n\n### 휴가 관리\n\n- **Google Calendar 자동 감지** — SKILL.md가 Claude Code에게 Google Calendar 휴가 이벤트 확인 지시\n- **CLI 수동 등록** — `config --vacation 2026-04-10 2026-04-11`\n- **휴가 키워드** (한국어 + 영어): 휴가/vacation/연차/PTO/병가/sick/반차/half-day\n- **휴가 유형**: vacation / sick / holiday / half / other\n- **보고서 연동** — `circle`에서 휴가일에 [vacation] 라벨 표시, `out`은 휴가일 자동 스킵\n\n### 설정 위저드\n\n`sincenety config --setup`으로 대화형 3선택 위저드 실행:\n1. Gmail SMTP (앱 비밀번호 URL 안내 포함)\n2. Resend API\n3. Custom SMTP\n\n설정 완료 시 연결 테스트 자동 실행.\n\n### Gmail MCP 연동\n\nClaude Code 안에서 `gmail_create_draft` MCP 도구로 zero-config 이메일 발송. SMTP 자격증명 불필요 — Claude Code가 Gmail에 직접 초안 작성. `out --render-only`로 MCP 경로용 HTML 출력.\n\n### 설정 관리\n\n`sincenety config`를 인자 없이 실행하면 ANSI 설정 상태 테이블을 표시합니다. 휴가 등록, 이메일 provider 선택 (Gmail/Resend/custom SMTP) 등을 지원합니다.\n\n### 범위 선택 (Global / Project)\n\n이 기기의 **모든 프로젝트**를 추적할지, **특정 프로젝트만** 추적할지 선택합니다:\n\n- **Global 모드** — 모든 Claude Code 세션 수집\n- **Project 모드** — 특정 프로젝트 경로의 세션만 필터링\n\n초기 설정 시(`npm install -g`) 또는 첫 `npx sincenety` 실행 시 선택. `~/.sincenety/scope.json`에 저장됩니다.\n\n### 클라우드 동기화 (Cloudflare D1)\n\nCloudflare D1을 통한 멀티머신 데이터 통합:\n\n- **로컬 우선**: 암호화된 로컬 DB가 원본 (source of truth)\n- **`sincenety sync`**로 로컬 데이터를 중앙 D1 데이터베이스에 push (push / pull-config / status / init)\n- **자동 동기화**: `out` 완료 후 자동 sync (non-fatal -- 네트워크 오류가 이메일 발송을 차단하지 않음)\n- **공유 설정**: SMTP 설정 한 번이면 `sync --pull-config`로 새 머신에서 즉시 사용\n- **머신 ID**: 하드웨어 기반 자동 감지 (아래 참조), `config --machine-name`으로 커스텀 식별\n- **의존성 무추가**: D1 REST API는 native `fetch` 사용 -- 새 패키지 없음\n\n### 주간/월간 보고 (v0.8.8+)\n\n**v0.8.8에서 휴리스틱 주간/월간 baseline 자동 생성을 완전히 제거했습니다.** 이전(v0.8.4)의 자동 생성 경로는 daily `outcomes`/`flows`를 `\"\\n\"`·`\" → \"`로 이어붙이던 텍스트-머지로, 품질이 낮은 주간/월간 요약이 조용히 발송되는 문제가 있었습니다. 통째로 삭제됨.\n\n- **Skill 경로로만 생성**: weekly/monthly row는 **오직** `/sincenety` 스킬이 `circle --save --type weekly|monthly`를 통해 만듭니다. Claude Code가 해당 기간의 daily 요약 전체를 맥락으로 삼아 고품질 요약을 작성한 뒤 저장 — CLI가 자체적으로 주간/월간 내용을 만들어내지 않습니다.\n- **`outw` / `outm` 에러 계약**: 대상 row가 없거나 `sessions` 배열이 비어 있으면 `runOut`이 `skipped`가 아닌 **명확한 에러**를 냅니다 (`\"weekly report row for \u003cdate\u003e not found. Run /sincenety to generate...\"`). cron은 exit code 1로 감지.\n- **매 실행 시 이번 주 daily 재요약** (v0.8.9): `runCircle`은 이번 주(월~오늘)의 daily를 `gather_reports.data_hash` 변경 여부와 무관하게 매번 강제로 재요약합니다. v0.8.8에서는 이번 달까지 함께 강제 재요약했지만 매 실행마다 출력이 너무 길고 토큰 소비가 과도해, v0.8.9에서 월 단위는 제외. monthly row는 월말 또는 필요 시 skill 경로에서만 갱신합니다.\n- **발송본 보호 유지**: `emailedAt != null`인 daily/weekly/monthly row는 위 강제 refresh 규칙에서도 덮어쓰이지 않습니다.\n- **제거된 설정/플래그**: `pipeline_mode` config 키와 `--mode` / `--pipeline-mode` CLI 플래그 삭제. `--pipeline-mode`는 한 줄 deprecation 경고만 출력하고 아무 동작도 하지 않음.\n\n### 크로스 디바이스 통합 리포트\n\n**v0.8.0** — 여러 기기(예: Mac + Linux)에서 작업하면 모든 기기의 세션이 자동으로 하나의 일일보고로 합쳐집니다:\n\n- **Push-before-pull**: 내 데이터를 D1에 먼저 올린 후, 다른 기기의 세션을 가져와 통합\n- **Circle 크로스 디바이스 머지**: `circle`(AI 요약) 단계에서 D1의 다른 기기 세션을 pull하여 전체 기기의 작업을 통합 요약 — 로컬 작업만이 아닌 모든 기기의 작업을 포괄\n- **무조건 발신 정책**: `out`은 다른 기기에서 이미 발송했더라도 항상 이메일 발신 — skip 없음, 중복 체크 없음\n- **세션 제목 머지**: 같은 `프로젝트명 + 제목`의 세션을 자동 머지 — 통계 합산, 가장 상세한 wrapUp 채택, flow 서술 연결\n- **Graceful fallback**: D1 연결 불가 시 기존 단일 기기 동작으로 자연스럽게 전환\n- **타이틀 추출 개선**: 슬래시 명령(/sincenety 등)으로 시작하는 세션에 의미 있는 폴백 제목 부여\n\n### Cloudflare API Token 발급 방법\n\n1. **토큰 생성 페이지 접속**: [https://dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)\n2. **\"Create Token\"** → **\"Custom token\"** (맨 아래 \"Get started\") 선택\n3. **권한 설정**:\n\n| 권한 | 접근 수준 | 용도 |\n|------|----------|------|\n| 계정 / **D1** | **Edit** | DB 생성 + 읽기/쓰기 |\n| 계정 / **Workers AI** | **Read** | AI 요약 모델 호출 (Qwen3-30B) |\n| 계정 / **계정 설정** | **Read** | 계정 자동 탐지 (`--d1-token` 설정 시) |\n\n\u003e **3개 모두 필수입니다.** 계정 설정 Read가 없으면 `--d1-token`으로 자동 설정 시 계정을 찾을 수 없습니다.\n\n4. **Account Resources** → Include → 본인 계정 선택\n5. **\"Create Token\"** → 토큰 복사 (**한 번만 표시됩니다!**)\n\n\u003e 이 토큰 하나로 D1 (중앙 DB) + Workers AI (요약 엔진) + sync (동기화)가 전부 동작합니다. 별도 API 키가 필요 없습니다.\n\n### Token-Only D1 설정\n\n토큰 하나면 나머지는 전부 자동:\n\n```bash\nsincenety config --d1-token cfp_xxxxxxxx\n# ✅ Account auto-detected\n# ✅ D1 database auto-created/connected\n# ✅ machine_id auto-detected (hardware UUID-based)\n# ✅ Workers AI auto-enabled (Qwen3-30B)\n# ✅ D1 schema setup complete!\n```\n\n### 자동 머신 ID\n\n하드웨어 기반 머신 식별 — 설정 불필요:\n\n| 플랫폼 | 소스 | 특성 |\n|--------|------|------|\n| macOS | IOPlatformUUID | 하드웨어 고유, 재설치 불변 |\n| Linux | /etc/machine-id | OS 고유 |\n| Windows | MachineGuid | 설치 고유 |\n\n- **포맷**: `mac_a1b2c3d4_username`\n- 사용자가 아무것도 안 해도 자동 감지\n- 같은 기기면 항상 같은 ID\n- D1 동기화 머신 레지스트리 (`machines` 테이블)에서 사용\n\n### 암호화 저장\n\n모든 데이터는 AES-256-GCM으로 암호화되어 `~/.sincenety/sincenety.db`에 저장됩니다. 머신 바운드 키(hostname + username + 랜덤 salt)를 기본으로 사용합니다.\n\n---\n\n## 설치 및 사용\n\nsincenety 실행 방법은 두 가지입니다: **npx** (설치 없음) 또는 **글로벌 설치**.\n\n### 방법 A: npx (처음 사용 / 원샷 실행 권장)\n\n\u003e **첫 실행 시 세 가지 플래그가 모두 필수입니다.** 없으면 설정 안내만 표시하고 종료합니다.\n\n**사전 준비 — 토큰 2개를 먼저 발급하세요:**\n\n1. **Cloudflare D1 API Token** — [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens)\n   - Custom token 생성, 아래 권한 3개 필수:\n\n   | 권한 | 액세스 | 용도 |\n   |------|--------|------|\n   | Account / **D1** | **Edit** | DB 생성 + 읽기/쓰기 |\n   | Account / **Workers AI** | **Read** | AI 요약 (Qwen3-30B) |\n   | Account / **Account Settings** | **Read** | 계정 자동 탐지 |\n\n2. **Resend API Key** — [resend.com/api-keys](https://resend.com/api-keys)\n   - 무료: 100통/일 (일일보고에 충분)\n\n**실행:**\n\n```bash\nnpx sincenety --token \u003cD1_TOKEN\u003e --key \u003cRESEND_KEY\u003e --email you@example.com\n```\n\n이 한 줄로:\n- D1 토큰 저장 → Cloudflare 계정 자동 감지 → DB 생성 → 스키마 설정\n- Resend API 키 + 수신 이메일 저장\n- 전체 파이프라인 실행: **air → circle → out**\n\n**이후 실행** — 설정이 `~/.sincenety/`에 저장되므로:\n\n```bash\nnpx sincenety\n```\n\n### 방법 B: 글로벌 설치 (일상적 사용 권장)\n\n```bash\nnpm install -g sincenety@latest\n```\n\n설치 후 대화형 셋업 위저드가 자동 실행됩니다:\n\n```\n  ┌──────────────────────────────────────────────┐\n  │  sincenety — Initial Setup                   │\n  └──────────────────────────────────────────────┘\n\n  ── Step 1/3: Scope ─────────────────────────────\n    1) Global   — 이 기기의 모든 Claude Code 프로젝트 추적\n    2) Project  — 특정 프로젝트만 추적\n\n  ── Step 2/3: D1 Cloud Sync ─────────────────────\n    Cloudflare API 토큰 생성 가이드 포함:\n      Account | Workers AI       | Read\n      Account | D1               | Edit\n      Account | Account Settings | Read\n\n  ── Step 3/3: Email Delivery ────────────────────\n    1) Gmail SMTP  (앱 비밀번호 필요)\n    2) Resend API  (resend.com API 키)\n    3) Custom SMTP\n```\n\n설정 완료 후:\n\n```bash\nsincenety\n```\n\n\u003e **참고**: 셋업 위저드는 최초 설치 시에만 실행됩니다. 이후 업데이트 시 기존 설정이 유지됩니다. CI/Docker 등 비대화형 환경에서는 위저드가 스킵되며, `sincenety config --setup`으로 수동 설정할 수 있습니다.\n\n### 소스 빌드\n\n```bash\ngit clone https://github.com/pathcosmos/sincenety.git\ncd sincenety\nnpm install \u0026\u0026 npm run build\nnpm link\n```\n\n### 설정 확인\n\n```bash\nsincenety config\n# 모든 설정을 ✅/❌ 상태로 표시\n# AI summary: ai_provider = auto (auto → cloudflare)\n```\n\n### 기본 — 전체 파이프라인\n\n```bash\n# 전체 파이프라인 실행: air → circle → out\nsincenety\n\n# D1 또는 이메일 미설정 시 help + 설정 안내 표시\n```\n\n### air — 기록 수집\n\n```bash\n# 갈무리 (checkpoint 기반 백필, 첫 실행 = 90일)\nsincenety air\n\n# 커스텀 history.jsonl 경로 지정\nsincenety air --history /path/to/history.jsonl\n\n# JSON 출력 (날짜별 구조화 데이터)\nsincenety air --json\n```\n\n### circle — AI 요약 파이프라인\n\n```bash\n# air 실행 + finalization 상태 확인\nsincenety circle\n\n# AI 요약용 세션 데이터 JSON 출력 (SKILL.md 연동)\nsincenety circle --json\n\n# Workers AI 요약 포함 JSON 출력 (SKILL.md cloudflare 모드)\nsincenety circle --json --summarize\n\n# AI가 생성한 요약을 DB에 저장 (stdin JSON)\nsincenety circle --save \u003c summary.json\nsincenety circle --save --type weekly \u003c weekly_summary.json\nsincenety circle --save --type monthly \u003c monthly_summary.json\n```\n\n### config — 설정 관리\n\n```bash\n# 대화형 설정 위저드 (Gmail SMTP / Resend / Custom SMTP)\nsincenety config --setup\n\n# 현재 설정 상태 표시 (ANSI 테이블)\nsincenety config\n\n# 이메일 설정\nsincenety config --email you@gmail.com\nsincenety config --smtp-user sender@gmail.com\nsincenety config --smtp-pass       # 프롬프트에서 비밀번호 입력\nsincenety config --provider resend\nsincenety config --resend-key rk_...\n\n# AI provider (Claude Code 환경 제어)\nsincenety config --ai-provider cloudflare   # Workers AI\nsincenety config --ai-provider anthropic    # Claude API\nsincenety config --ai-provider claude-code  # Claude Code 직접 요약\nsincenety config --ai-provider auto         # 자동 감지 (기본값)\n\n# 휴가 관리\nsincenety config --vacation 2026-04-10 2026-04-11\nsincenety config --vacation-list\nsincenety config --vacation-clear 2026-04-10\n```\n\n\u003e Gmail 앱 비밀번호 생성: https://myaccount.google.com/apppasswords\n\n### out — 스마트 이메일 발신\n\n```bash\n# 스마트 발신 (요일 + 미발송 캐치업 자동 판단)\nsincenety out\n\n# 미리보기 (발송 안 함)\nsincenety out --preview\n\n# HTML JSON 출력 (Gmail MCP용)\nsincenety out --render-only\n\n# 발송 내역 조회\nsincenety out --history\n\n# 강제 발신\nsincenety outd    # 일일보고\nsincenety outw    # 주간보고\nsincenety outm    # 월간보고\n\n# 특정 날짜 지정 (yyyyMMdd)\nsincenety outd --date 20260408   # 4월 8일 일간보고\nsincenety outw --date 20260408   # 4월 6-12일 주간보고\nsincenety outm --date 20260408   # 2026년 4월 월간보고\nsincenety out --date 20260408    # 4월 8일 기준 스마트 발신\n```\n\n### sync — 클라우드 동기화 (Cloudflare D1)\n\n```bash\n# D1 설정\nsincenety config --d1-account ACCOUNT_ID --d1-database DB_ID --d1-token TOKEN\nsincenety config --machine-name \"office-mac\"\n\n# 동기화\nsincenety sync --init          # D1 스키마 생성\nsincenety sync                 # 로컬 → D1 push\nsincenety sync --pull-config   # D1 → 로컬 공유 설정\nsincenety sync --status        # 상태 확인\n```\n\n### Claude Code Skill (`/sincenety`)\n\nClaude Code 안에서 `/sincenety`로 직접 호출하여 AI 기반 일일보고를 생성합니다.\n\n#### 설치\n\n1. **CLI 설치** (데이터 수집 엔진):\n\n```bash\nnpm install -g sincenety@latest\n```\n\n2. **Skill 등록** (Claude Code에 `/sincenety` 명령 등록):\n\n```bash\nmkdir -p ~/.claude/skills/sincenety\ncp node_modules/sincenety/src/skill/SKILL.md ~/.claude/skills/sincenety/SKILL.md\n```\n\n또는 글로벌 설치인 경우:\n\n```bash\nmkdir -p ~/.claude/skills/sincenety\ncp \"$(npm root -g)/sincenety/src/skill/SKILL.md\" ~/.claude/skills/sincenety/SKILL.md\n```\n\n3. **최신 버전 업데이트**:\n\nClaude Code 안에서:\n```\n! npm install -g sincenety@latest\n```\n\n#### 동작 원리\n\nClaude Code에서 `/sincenety` 입력 시:\n\n1. **데이터 수집** — `air`가 checkpoint 기반 백필로 모든 세션 수집\n2. **JSON 출력** — `circle --json`이 대화 턴 포함 세션 데이터 출력\n3. **AI 요약** — Claude Code 자체가 topic/outcome/flow/significance 생성\n4. **DB 저장** — `circle --save`로 `daily_reports` 테이블에 저장\n5. **이메일 발송** — 설정 시 AI 요약이 반영된 HTML 이메일 발송\n\n핵심: Claude Code **자체가** AI이므로 외부 API 키가 필요 없습니다.\n\n#### 이메일 설정 (선택)\n\n```bash\nsincenety config --email you@gmail.com\nsincenety config --smtp-user you@gmail.com\nsincenety config --smtp-pass    # Gmail 앱 비밀번호 입력 프롬프트\n```\n\n\u003e Gmail 앱 비밀번호 생성: https://myaccount.google.com/apppasswords\n\n---\n\n## 아키텍처\n\n```\nsincenety/\n├── src/\n│   ├── cli.ts                  # CLI 진입점 (default + air/circle/out/outd/outw/outm/sync/config)\n│   ├── postinstall.ts          # postinstall 셋업 위저드 (scope → D1 → email)\n│   ├── core/\n│   │   ├── air.ts              # Phase 1: 날짜별 갈무리 (백필 + 해시)\n│   │   ├── circle.ts           # Phase 2: LLM 요약 파이프라인 (finalization + 저장)\n│   │   ├── out.ts              # Phase 3: 스마트 이메일 발신 (out/outd/outw/outm)\n│   │   ├── gatherer.ts         # 갈무리 핵심 로직 (파싱→그룹핑→저장)\n│   │   ├── summarizer.ts       # AI 요약 라우터 (Workers AI / Claude API / 휴리스틱)\n│   │   └── ai-provider.ts      # AI provider 감지 및 라우팅 (cloudflare/anthropic/claude-code)\n│   ├── parser/\n│   │   ├── history.ts          # ~/.claude/history.jsonl 스트리밍 파서\n│   │   └── session-jsonl.ts    # 세션 JSONL 파서 (토큰/모델/타이밍/대화턴 추출)\n│   ├── grouper/\n│   │   └── session.ts          # sessionId+project 기준 그룹핑\n│   ├── storage/\n│   │   ├── adapter.ts          # StorageAdapter 인터페이스\n│   │   └── sqljs-adapter.ts    # sql.js 구현 (암호화 DB, v4 자동 마이그레이션)\n│   ├── encryption/\n│   │   ├── key.ts              # PBKDF2 키 파생 (머신 바운드 + passphrase)\n│   │   └── crypto.ts           # AES-256-GCM encrypt/decrypt\n│   ├── report/\n│   │   ├── terminal.ts         # 터미널 테이블 출력 (유니코드 박스 드로잉, 영문 출력)\n│   │   └── markdown.ts         # 마크다운 리포트 생성\n│   ├── email/\n│   │   ├── sender.ts           # nodemailer 이메일 발송\n│   │   ├── renderer.ts         # HTML 이메일 렌더러 (보고서 → HTML, 크로스 디바이스 머지)\n│   │   ├── merge-sessions.ts   # 세션 머지 유틸리티 (프로젝트 단위 세션 통합)\n│   │   ├── resend.ts           # Resend API 이메일 provider\n│   │   ├── provider.ts         # 이메일 provider 추상화 (Gmail MCP/Resend/SMTP)\n│   │   └── template.ts         # Bright 컬러코딩 HTML 이메일 템플릿\n│   ├── vacation/\n│   │   ├── manager.ts          # 휴가 CRUD (등록/조회/삭제/확인)\n│   │   └── detector.ts         # 휴가 키워드 감지 (한국어+영어)\n│   ├── config/\n│   │   ├── setup-wizard.ts     # 대화형 3선택 설정 위저드\n│   │   └── scope.ts            # 범위 설정 (global/project) 읽기/쓰기/프롬프트\n│   ├── cloud/\n│   │   ├── d1-client.ts        # Cloudflare D1 REST API 클라이언트\n│   │   ├── d1-schema.ts        # D1 스키마 정의 및 마이그레이션\n│   │   ├── d1-auto-setup.ts    # Token-only 자동 설정 (계정/DB 감지)\n│   │   ├── cf-ai.ts            # Cloudflare Workers AI 클라이언트 (Qwen3-30B)\n│   │   └── sync.ts             # 동기화 로직 (push/pull/status/init)\n│   ├── util/\n│   │   └── machine-id.ts       # 크로스플랫폼 하드웨어 ID 감지\n│   ├── scheduler/\n│   │   └── install.ts          # launchd/cron 자동 설치 (비활성화)\n│   └── skill/SKILL.md          # Claude Code skill 정의\n├── tests/\n│   ├── encryption.test.ts      # 암호화 테스트 (26개)\n│   ├── migration-v4.test.ts    # DB v3→v4 마이그레이션 테스트 (7개)\n│   ├── air.test.ts             # air 명령 테스트 (7개)\n│   ├── circle.test.ts          # circle 명령 테스트 (10개)\n│   ├── out.test.ts             # out 명령 테스트 (28개)\n│   ├── vacation.test.ts        # 휴가 관리 테스트 (13개)\n│   ├── d1-client.test.ts       # D1 클라이언트 테스트\n│   ├── sync.test.ts            # 동기화 테스트\n│   ├── cf-ai.test.ts           # Cloudflare Workers AI 테스트\n│   └── machine-id.test.ts      # 머신 ID 감지 테스트\n├── package.json\n├── tsconfig.json\n├── CLAUDE.md\n└── README.md\n```\n\n### 설치 흐름 (Install Flow)\n\n```\nnpm install -g sincenety@latest\n        │\n        ▼\n┌─ postinstall.js ─────────────────────────────────┐\n│                                                   │\n│  TTY 체크 ───→ TTY 없음? → \"config --setup 실행\"  │\n│       │                                           │\n│       ▼ (TTY 있음)                                │\n│  이미 설정 완료? ──→ 예 → \"설정 유지됨\"             │\n│       │                                           │\n│       ▼ (아니오)                                   │\n│                                                   │\n│  Step 1: 범위 선택                                 │\n│  ┌────────────────────────┐                       │\n│  │ 1) Global (전체)       │                       │\n│  │ 2) Project (특정 경로) │                       │\n│  └───────┬────────────────┘                       │\n│          │ → ~/.sincenety/scope.json              │\n│          ▼                                        │\n│  Step 2: D1 클라우드 동기화                        │\n│  ┌────────────────────────┐                       │\n│  │ D1 API 토큰 입력       │                       │\n│  │ → autoSetupD1()        │                       │\n│  │ → ensureD1Schema()     │                       │\n│  └───────┬────────────────┘                       │\n│          │ → ~/.sincenety/sincenety.db            │\n│          ▼                                        │\n│  Step 3: 이메일 설정                               │\n│  ┌────────────────────────┐                       │\n│  │ 1) Gmail SMTP          │                       │\n│  │ 2) Resend API          │                       │\n│  │ 3) Custom SMTP         │                       │\n│  └───────┬────────────────┘                       │\n│          │ → ~/.sincenety/sincenety.db            │\n│          ▼                                        │\n│  ✅ 준비 완료                                      │\n└───────────────────────────────────────────────────┘\n```\n\n### 실행 흐름 (Run Flow)\n\n```\n$ sincenety [--token T --key K --email E]\n        │\n        ▼\n   Scope 체크 ───→ 미설정? → 프롬프트 (global/project)\n        │\n        ▼\n   파라미터 체크 ──→ D1/email 미설정? → 설정 가이드 + exit\n        │\n        ▼\n┌─ runOut(scope) ──────────────────────────────────┐\n│                                                   │\n│  ┌─ air (환기) ──────────────────────────────┐    │\n│  │ ~/.claude/history.jsonl                   │    │\n│  │   → 세션 목록 (sessionId + project)       │    │\n│  │ ~/.claude/projects/[p]/[id].jsonl         │    │\n│  │   → 토큰 / 모델 / 타이밍 / 대화턴        │    │\n│  │                                           │    │\n│  │ scope 필터 (project 모드)                 │    │\n│  │ 날짜별 그룹핑 (자정 경계)                 │    │\n│  │ checkpoint 백필 + data hash               │    │\n│  │   → gather_reports DB                     │    │\n│  └───────────────────────────────────────────┘    │\n│                 │                                 │\n│                 ▼                                 │\n│  ┌─ circle (순환 정화) ──────────────────────┐    │\n│  │ 자동 확정 처리                            │    │\n│  │   (전날 / 지난주 / 전월)                  │    │\n│  │ D1 크로스 디바이스 세션 pull + 머지        │    │\n│  │ Workers AI 요약 (Qwen3-30B)               │    │\n│  │   → daily_reports DB (전체 기기)           │    │\n│  └───────────────────────────────────────────┘    │\n│                 │                                 │\n│                 ▼                                 │\n│  ┌─ D1 pre-sync ────────────────────────────┐     │\n│  │ 로컬 → D1 push (내 데이터 먼저)          │     │\n│  └──────────────────────────────────────────┘     │\n│                 │                                 │\n│                 ▼                                 │\n│  ┌─ out (스마트 발신) ───────────────────────┐    │\n│  │ daily  — 항상 발송                        │    │\n│  │ weekly — 금요일 (또는 캐치업)             │    │\n│  │ monthly — 월말 (또는 캐치업)              │    │\n│  │ --date yyyyMMdd — 특정 날짜 지정          │    │\n│  │                                           │    │\n│  │ D1 크로스 디바이스 세션 pull + 머지        │    │\n│  │ 프로젝트 단위 세션 머지 (×N)               │    │\n│  │                                           │    │\n│  │ → Gmail MCP / Resend /                    │    │\n│  │   Gmail SMTP / Custom SMTP                │    │\n│  └───────────────────────────────────────────┘    │\n│                 │                                 │\n│                 ▼                                 │\n│  ┌─ D1 post-sync ───────────────────────────┐     │\n│  │ 이메일 발송 로그 → D1 push               │     │\n│  └──────────────────────────────────────────┘     │\n│                                                   │\n└───────────────────────────────────────────────────┘\n        │\n        ▼\n   ✅ sincenety complete — N sent, N skipped\n```\n\n### 로컬 DB — 완전 명세\n\n**파일**: `~/.sincenety/sincenety.db` (AES-256-GCM 암호화 blob, 파일 권한 `0600`, 디렉토리 권한 `0700`)\n**엔진**: `sql.js` — WASM 컴파일된 SQLite. native 의존성 0. DB 파일 전체가 open 시 메모리로 복호화되고, 메모리에서 변경된 뒤, close 시 재암호화되어 전체 파일이 다시 쓰임. 디스크에 점진적 INSERT는 없으며, 매 실행마다 암호화 blob 전체가 재작성됨.\n**사이드카**: `~/.sincenety/sincenety.salt` — 32바이트 암호학적 랜덤 salt. 최초 실행 시 **1회** 생성되어 PBKDF2 키 파생에 사용. 이 파일이 삭제되면 DB는 영구적으로 복호화 불가.\n**DB 확인**: `file ~/.sincenety/sincenety.db` 결과가 `data`(불투명)로 나와야 정상. `SQLite 3.x database`로 나오면 암호화가 깨진 것으로 평문 유출 상태.\n\n#### 로컬 DB를 유지하는 이유 (설계 근거)\n\n로컬 DB는 **파생 산출물(derived artifact)**. 원천(source of truth)은 언제나 `~/.claude/history.jsonl` + `~/.claude/projects/*.jsonl`이고, 원칙적으로는 매 실행마다 원천에서 전부 재구성 가능함. 그럼에도 로컬 DB를 유지하는 이유는, 단순 파일 재구성으로는 깔끔히 해결되지 않는 세 가지 역할 때문:\n\n1. **멱등성(Idempotency) 경계** — `sincenety`는 하루에 여러 번 실행되는 패턴(크론 10:00, 수동 15:00, 자동 일 마감)을 전제로 설계됨. `sessions`의 복합 PK `(session_id, project)`와 `daily_reports`의 `UNIQUE(report_date, report_type)` 제약이 모든 재실행을 안전하게 만듦. DB가 없다면 (a) 매 실행마다 중복 리포트/중복 이메일이 생기거나 (b) 별도 dedupe 인덱스를 디스크에 직접 유지해야 함 — 후자는 결국 \"제대로 안 만든 DB\"에 불과.\n\n2. **발송 상태의 유일한 권위(authority)** — `daily_reports.emailed_at`이 \"이 리포트가 이미 발송됐는가?\"에 대한 단일 진실 공급원. `circle.ts`의 `autoSummarize`와 `circleSave` 전반에서 `emailedAt != null`인 row는 덮어쓰기로부터 명시적으로 보호됨 — v0.8.9의 \"이번 주 매번 강제 재요약\" 규칙(이전 v0.8.8의 \"이번 주/달\" 규칙) 하에서도 동일. `email_logs`는 append-only 감사 로그로서, 성공·실패 발송 전부가 subject/recipient/provider/error와 함께 기록됨.\n\n3. **크로스 디바이스 머지의 피봇(pivot)** — `sync push`(발송 전)가 이 기기의 `daily_reports` 행들을 Cloudflare D1로 업로드하고, `sync pull`이 다른 기기들이 작성한 행을 내려받음. 이메일 렌더러의 머지 로직은 로컬 행과 pull한 행을 `(report_date, project_name)` 기준으로 조인하고 세션은 `(project_name, title_normalized)`로 dedupe. 로컬 DB가 없으면 \"이 기기의 시점(view)\"이란 개념 자체가 성립하지 않아 push 불가, 원격 행을 merge할 안정적 피봇도 없음.\n\n**DB에 저장하지 않는 것** (의도적 선택): 대화 전문, 코드 내용, 툴 호출 payload 전체. 메타데이터(카운트·타이밍·토큰·제목·설명·짧은 요약)만 저장하여, 만에 하나 키 파생이 유출돼도 피해 범위를 제한.\n\n**로컬 DB가 실제로 불필요한 경우**: 1기기만 사용 + 이메일 사용 안 함 + sync 사용 안 함 + `--json` 출력만 Claude Code에 파이프하는 사용자. 이 케이스에서는 DB가 비용만 추가. 그 외 (멀티 디바이스, 스케줄 발송, 주/월 롤업) 모든 사용자에게는 위 3가지 역할을 DB 없이 구현하려면 결국 처음부터 다시 만들어야 함.\n\n#### 저장 디렉토리 구조\n\n```\n~/.sincenety/\n├── sincenety.db       # 암호화된 SQLite blob (이 문서가 설명하는 대상)\n├── sincenety.salt     # 32바이트 PBKDF2 salt (0600)\n└── machine-id         # D1 행 귀속을 위한 안정적 기기 식별자\n```\n\n#### 암호화 엔벨롭(envelope)\n\n```\n[4B 매직 \"SNCT\"] [12B IV] [ciphertext (가변)] [16B GCM 인증 태그]\n```\n\n- **알고리즘**: AES-256-GCM (AEAD — 복호화 시 ciphertext 변조 검출)\n- **키 파생**: PBKDF2-SHA256, **100,000 iterations**, 32바이트 출력\n- **키 재료**: 기본값 `hostname ∥ username ∥ salt` (머신 바운드), 또는 사용자 지정 passphrase\n- **IV**: encrypt마다 랜덤 12바이트, 동일 키에서 재사용 금지\n- **인증 태그**: 16바이트, 복호화마다 검증 — 변조 시 예외 발생, **빈 DB로 조용히 폴백하지 않음**\n\n#### 스키마 버전 — v4 (현재)\n\n스키마 버전은 `config.value` 테이블의 `schema_version` 키에 저장. open 시 `applySchema()`가 현재 버전을 읽어 전진 전용(forward-only) 마이그레이션 실행:\n\n| From → To | 마이그레이션 요약 |\n|-----------|------------------|\n| `v1 → v2` | `ALTER TABLE sessions ADD COLUMN` × 14 (토큰, 타이밍 세부, 제목, 설명, 카테고리, 태그, 모델). `gather_reports`, `config` 테이블 신규. |\n| `v2 → v3` | `daily_reports` 테이블 신규 (`UNIQUE(report_date, report_type)` 제약의 AI 요약 저장소). |\n| `v3 → v4` | `gather_reports`에 `report_date`, `data_hash`, `updated_at` 추가. `daily_reports`에 `status`, `progress_label`, `data_hash` 추가. `vacations`, `email_logs` 테이블 신규. `idx_gather_report_date` 유니크 인덱스 추가. |\n\n마이그레이션은 `ALTER TABLE ADD COLUMN`만 사용(`DROP` 없음) — 신버전에서 쓴 DB를 구버전으로 내려도 깨지지 않도록. `schema_version`이 알 수 없는 값이면 \"fresh install\"로 간주하고 v1부터 재구축.\n\n#### 테이블별 컬럼 상세\n\n##### `sessions` (22컬럼) — 작업 세션 원천 레코드\n\n복합 PK `(id, project)`. 하나의 Claude Code 세션(하나의 `sessionId` × 하나의 프로젝트 디렉토리)이 한 행. 매 갈무리 실행마다 upsert.\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | TEXT NOT NULL | Claude Code `sessionId` (UUID, `~/.claude/sessions/\u003cid\u003e.json`에서 추출) |\n| `project` | TEXT NOT NULL | 프로젝트 절대 경로 (세션 시작 시의 `cwd`) |\n| `project_name` | TEXT NOT NULL | `basename(project)` — 표시용 + 동일 프로젝트 머지 키 |\n| `started_at` | INTEGER NOT NULL | Unix epoch ms — 세션 첫 메시지 시각 |\n| `ended_at` | INTEGER NOT NULL | Unix epoch ms — 세션 마지막 메시지 시각 |\n| `duration_minutes` | REAL DEFAULT 0 | `(ended_at - started_at) / 60000`, 리포트 쿼리용 사전 계산값 |\n| `message_count` | INTEGER NOT NULL DEFAULT 0 | 전체 메시지 수 (user + assistant + tool) |\n| `user_message_count` | INTEGER DEFAULT 0 | 사용자 작성 메시지만 |\n| `assistant_message_count` | INTEGER DEFAULT 0 | 어시스턴트 응답만 |\n| `tool_call_count` | INTEGER DEFAULT 0 | 툴 호출 수 (Read, Edit, Bash, …) |\n| `input_tokens` | INTEGER DEFAULT 0 | 세션 누적 |\n| `output_tokens` | INTEGER DEFAULT 0 | 세션 누적 |\n| `cache_creation_tokens` | INTEGER DEFAULT 0 | 프롬프트 캐시 쓰기 |\n| `cache_read_tokens` | INTEGER DEFAULT 0 | 프롬프트 캐시 히트 |\n| `total_tokens` | INTEGER DEFAULT 0 | 위 4개 합계를 비정규화 저장 — 리포트 집계에 직접 사용 |\n| `title` | TEXT | AI 생성 또는 휴리스틱 제목 (≤80자) |\n| `summary` | TEXT | 짧은 요약 (1–2문장) |\n| `description` | TEXT | 세션에서 있었던 일의 상세 설명 |\n| `category` | TEXT | 분류 (feat/fix/docs/refactor/chore 등) |\n| `tags` | TEXT | 콤마 구분 키워드 태그 |\n| `model` | TEXT | 주 사용 모델 (예: `claude-opus-4-6`, `claude-sonnet-4-6`) |\n| `created_at` | INTEGER NOT NULL | DB 행 생성 ms — 세션 시간이 아님 |\n\n**인덱스**: `idx_sessions_started` (`started_at`), `idx_sessions_project` (`project`), `idx_sessions_category` (`category`).\n\n**쓰기 경로**: `gatherer.ts` → `INSERT … ON CONFLICT(id, project) DO UPDATE`로 세션별 UPSERT. 토큰 카운터는 **덮어쓰기**(누적 합산 아님) — 원천 JSONL이 정답.\n\n##### `gather_reports` (실행 raw 로그)\n\n`sincenety` 갈무리 실행의 raw 마크다운 + JSON 출력을 저장. 운영상 필수는 아니고, 감사 로그 및 `--json` 재현성 목적으로 유지.\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | INTEGER PK AUTOINCREMENT | 대리 키 |\n| `gathered_at` | INTEGER NOT NULL | 실행 시각 (ms) |\n| `from_timestamp` | INTEGER NOT NULL | 갈무리 윈도우 시작 |\n| `to_timestamp` | INTEGER NOT NULL | 갈무리 윈도우 종료 |\n| `session_count` | INTEGER DEFAULT 0 | 이 실행의 세션 수 |\n| `total_messages` | INTEGER DEFAULT 0 | 메시지 총합 |\n| `total_input_tokens` | INTEGER DEFAULT 0 | |\n| `total_output_tokens` | INTEGER DEFAULT 0 | |\n| `report_markdown` | TEXT | 렌더된 터미널/마크다운 리포트 |\n| `report_json` | TEXT | 하류 `save-daily`에 전달되는 구조화 JSON |\n| `emailed_at` | INTEGER | Deprecated — `daily_reports.emailed_at`로 대체됨 |\n| `email_to` | TEXT | Deprecated |\n| `report_date` | TEXT *(v4)* | `YYYY-MM-DD` (윈도우 시작일) — 유니크 인덱스에 사용 |\n| `data_hash` | TEXT *(v4)* | `report_json`의 콘텐츠 해시; 입력이 같으면 해시 같음 → no-op 재작성 |\n| `updated_at` | INTEGER *(v4)* | 마지막 수정 시각 ms |\n\n**유니크 인덱스** `idx_gather_report_date` on `(report_date)` *(v4)* — 날짜당 raw 갈무리 리포트 1건, 재실행 시 같은 행을 갱신.\n\n##### `daily_reports` (AI 요약 — 일간/주간/월간)\n\n이메일로 나가는 콘텐츠와 크로스 디바이스 sync 교환의 권위적 원본. `(report_date, report_type)`마다 1행.\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | INTEGER PK AUTOINCREMENT | |\n| `report_date` | TEXT NOT NULL | `YYYY-MM-DD` 앵커 (주간=월요일, 월간=1일) |\n| `report_type` | TEXT NOT NULL DEFAULT `'daily'` | `daily` / `weekly` / `monthly` 중 하나 |\n| `period_from` | INTEGER NOT NULL | 윈도우 시작 (ms) |\n| `period_to` | INTEGER NOT NULL | 윈도우 종료 (ms) |\n| `session_count` | INTEGER DEFAULT 0 | 윈도우 내 세션 수 집계 |\n| `total_messages` | INTEGER DEFAULT 0 | 집계 |\n| `total_tokens` | INTEGER DEFAULT 0 | 집계 |\n| `summary_json` | TEXT NOT NULL | 세션별 `SummaryEntry` 배열의 직렬화 (title, overview, actions, tokens, project_name, …). 이메일 렌더러가 이 필드를 읽음. |\n| `overview` | TEXT | 일/주/월 전체의 메타 요약 (2–4문장) |\n| `report_markdown` | TEXT | CLI `report`용 사전 렌더된 마크다운 |\n| `created_at` | INTEGER NOT NULL | 행 생성 ms |\n| `emailed_at` | INTEGER | **null 체크**(`!= null`)로 덮어쓰기 가능 여부 판단. non-null = 이미 발송됨 = auto-summary가 덮어쓰면 안 됨. |\n| `email_to` | TEXT | 발송된 리포트의 수신자 주소 |\n| `status` | TEXT DEFAULT `'in_progress'` *(v4)* | 윈도우가 아직 열려있으면 `in_progress`, 완전히 마감되면(전날/전주/전월) `finalized`. `finalizePreviousReports`가 상태 전환을 담당. |\n| `progress_label` | TEXT *(v4)* | 사람 읽기 용 상태 라벨 (예: \"5/7 days of week\") |\n| `data_hash` | TEXT *(v4)* | 변경 감지용 콘텐츠 해시 — D1 sync는 원격 행과 해시가 같으면 push 생략 |\n\n**제약**: `UNIQUE(report_date, report_type)` — 멱등성 보장의 핵심.\n**인덱스**: `idx_daily_date`, `idx_daily_type`.\n\n##### `checkpoints`\n\n갈무리 실행마다 마지막 처리한 timestamp를 기록. 실제로는 현재 사용하지 않음(갈무리가 언제나 오늘 00:00부터 진행, 마지막 체크포인트 이후 증분 아님) — 하위 호환성과 향후 \"incremental since N\" 모드 가능성을 위해 유지.\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | INTEGER PK AUTOINCREMENT | |\n| `timestamp` | INTEGER NOT NULL | 마지막 처리 ms |\n| `created_at` | INTEGER NOT NULL | |\n\n##### `config` (key-value 설정)\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `key` | TEXT PK | 설정 이름 |\n| `value` | TEXT NOT NULL | 문자열 값 (필요 시 JSON 인코딩) |\n| `updated_at` | INTEGER NOT NULL | |\n\n알려진 키: `schema_version`, `email_to`, `smtp_user`, `smtp_pass`, `smtp_host`, `smtp_port`, `resend_key`, `d1_api_token`, `d1_account_id`, `d1_database_id`, `cf_ai_token`, `provider`, `ai_provider` (`cloudflare` | `anthropic` | `claude-code` | `auto`), `scope` (`global` | `project`). *(`pipeline_mode` 키는 v0.8.8에서 폐기되어 더 이상 읽히지 않으며, `--pipeline-mode` 플래그는 deprecation 경고만 출력합니다.)*\n\n##### `vacations`\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | INTEGER PK AUTOINCREMENT | |\n| `date` | TEXT NOT NULL UNIQUE | `YYYY-MM-DD` |\n| `type` | TEXT NOT NULL DEFAULT `'vacation'` | `vacation` / `holiday` / `sick` |\n| `source` | TEXT NOT NULL DEFAULT `'manual'` | `manual` / `auto` (세션 내용에서 키워드 감지) |\n| `label` | TEXT | 표시 라벨 (예: \"설 연휴\") |\n| `created_at` | INTEGER NOT NULL | |\n\n휴가 날짜는 `out` 실행 시 발송이 스킵됨. `date` 유니크 제약으로 중복 마킹 차단.\n\n##### `email_logs`\n\n모든 이메일 발송 시도의 append-only 감사 로그. 삭제하지 않음. 무한 증가하므로 필요 시 수동 절단.\n\n| 컬럼 | 타입 | 역할 |\n|------|------|------|\n| `id` | INTEGER PK AUTOINCREMENT | |\n| `sent_at` | INTEGER NOT NULL | 시도 시각 ms |\n| `report_type` | TEXT NOT NULL | `daily` / `weekly` / `monthly` |\n| `report_date` | TEXT NOT NULL | 리포트의 `YYYY-MM-DD` |\n| `period_from` | TEXT NOT NULL | 윈도우 시작 (ISO date) |\n| `period_to` | TEXT NOT NULL | 윈도우 종료 (ISO date) |\n| `recipient` | TEXT NOT NULL | 수신자 주소 |\n| `subject` | TEXT NOT NULL | 렌더된 제목 |\n| `body_html` | TEXT | 렌더된 HTML (실패 발송 시 null 가능) |\n| `body_text` | TEXT | 플레인텍스트 fallback 본문 |\n| `provider` | TEXT NOT NULL | `gmail-smtp` / `resend` / `gmail-mcp` |\n| `status` | TEXT NOT NULL DEFAULT `'sent'` | `sent` / `failed` |\n| `error_message` | TEXT | `status = 'failed'`일 때의 에러 상세 |\n\n**인덱스**: `idx_email_logs_sent` (`sent_at`), `idx_email_logs_report` (`report_date, report_type`).\n\n#### 읽기 경로 (DB가 실제로 어디에 쓰이나)\n\n| 명령 | 읽는 테이블 | 용도 |\n|------|------------|------|\n| `sincenety` (default) | `sessions`, `daily_reports`, `vacations`, `email_logs`, `config` | 풀 파이프라인 — 갈무리 → 요약 → 렌더 → 발송 |\n| `air` | `sessions`, `gather_reports` | Phase 1만 — 수집 \u0026 저장 |\n| `circle` | `sessions`, `daily_reports` | Phase 2만 — AI 요약 + finalize |\n| `out` / `outd` / `outw` / `outm` | `daily_reports`, `email_logs`, `vacations`, `config` | Phase 3만 — 스마트 이메일 발송 |\n| `report --date` / `--week` / `--month` | `daily_reports` | 저장된 요약을 터미널로 렌더 |\n| `sync push` | `daily_reports`, `config` | 내 행을 D1으로 업로드 |\n| `sync pull` | `daily_reports`, `config` | 다른 기기의 행을 내려받아 머지 |\n| `config` | `config` | 설정 조회/수정 |\n| `vacation` | `vacations` | 휴가 날짜 CRUD |\n\n**현재 미지원(알려진 갭)**: `sessions.title/description` 전문 검색, 프로젝트 단위 집계 뷰, 타임라인/히트맵 쿼리. 데이터는 이미 저장 중이고, 읽기 경로만 없음 — 향후 작업 후보.\n\n#### 백업과 복구\n\n- **백업 대상 아님** — DB는 `~/.claude/`에서 파생된 것. 손실되면 `sincenety --since \"2026-04-01\"`로 원천에서 재구축.\n- **예외**: `daily_reports.summary_json` (AI 요약)과 `email_logs`는 `~/.claude/`만으로는 **복원 불가** — LLM 요약 재실행이 필요하고 토큰 비용이 들어감. 이 두 테이블만 유의미한 백업 대상. Cloudflare D1 sync가 `daily_reports`의 원격 백업 역할.\n- **재해 복구 절차**: `sincenety.db` + `sincenety.salt` 삭제 → 재설치 → 재실행. 과거 email_logs와 LLM 이전 요약은 유실, 세션 메타데이터만 `~/.claude/`에서 재구축됨.\n\n---\n\n## 기술 스택\n\n| 구성요소 | 기술 |\n|---------|------|\n| 언어 | TypeScript (ESM, Node16 모듈) |\n| 런타임 | Node.js \u003e= 18 |\n| CLI | commander |\n| DB | sql.js (WASM SQLite, native 의존성 없음) |\n| 암호화 | Node.js 내장 crypto (AES-256-GCM) |\n| 이메일 | nodemailer (Gmail SMTP), Resend API |\n| 클라우드 | Cloudflare D1 REST API (native fetch, 추가 의존성 없음) |\n| AI 요약 | Cloudflare Workers AI (Qwen3-30B), 추가 의존성 없음 |\n| 테스트 | vitest (171개, 11개 테스트 파일) |\n\n### 의존성 (최소)\n\n```\ndependencies:\n  commander       # CLI 파싱\n  nodemailer      # 이메일 발송\n  sql.js          # WASM SQLite\n\ndevDependencies:\n  typescript\n  vitest\n  tsx\n  @types/node\n  @types/nodemailer\n```\n\n---\n\n## 개발 가이드\n\n### 빌드 및 실행\n\n```bash\nnpm install          # 의존성 설치\nnpm run build        # TypeScript 컴파일 (dist/)\nnpm run dev          # tsx로 개발 실행\nnpm test             # vitest 테스트 (171개)\nnode dist/cli.js     # 직접 실행\n```\n\n### 테스트\n\n```bash\n# 전체 테스트 (171개)\nnpm test\n\n# 개별 테스트\nnpx vitest run tests/encryption.test.ts   # 암호화 (26개)\nnpx vitest run tests/migration-v4.test.ts # DB 마이그레이션 (7개)\nnpx vitest run tests/air.test.ts          # air 명령 (7개)\nnpx vitest run tests/circle.test.ts       # circle 명령 (39개)\nnpx vitest run tests/out.test.ts          # out 명령 (47개)\n```\n\n### 로컬 npx 테스트\n\n```bash\nnpx .                # 현재 디렉토리를 npx로 실행\n```\n\n---\n\n## 개발 이력\n\n### v0.8.9 (2026-04-17) — 강제 재요약 범위 축소: 이번 주/달 → 이번 주만\n\n#### 배경\n\nv0.8.8에서 `runCircle`이 매 실행마다 **이번 주(월~오늘) + 이번 달(1일~오늘)** daily를 모두 강제 재요약하도록 했지만, 실제로는 `/sincenety` 기본 실행 한 번에 `♻️ \u003cdate\u003e re-summarizing` 라인이 20개 이상 출력되고 변경되지 않은 dailies까지 Workers AI 호출을 소비하는 문제가 있었음. 사용자 피드백 그대로: _\"이건 너무 지금 길잖아, 토큰 소비도 많을테구\"_ — 그리고 2026-04-17 시점에 2026-04-02 ~ 2026-04-09까지 줄줄이 재요약되는 실제 터미널 로그가 첨부됨.\n\n#### 변경\n\n- **`runCircle`**: 강제 재요약 대상 집합을 **이번 주만**(`forcedThisWeek`)으로 축소 (이전 `forcedWeekMonth`). 주석과 변수명 모두 좁혀진 범위를 반영.\n- **`circleJson`** (skill `--json` 경로): 동일 축소 — skill 클라이언트는 이제 \"이번 주 ∪ 변경된 날짜\"만 보게 되며, 이전의 \"이번 주 ∪ 이번 달 ∪ 변경된 날짜\"는 폐기.\n- **로그 메시지**: `♻️ \u003cdate\u003e re-summarizing (current week/month — forced refresh)` → `... (current week — forced refresh)`.\n- **데드코드 제거**: `datesInCurrentMonthUpToToday` 헬퍼를 `src/core/circle.ts`에서 삭제 (호출처 없음).\n- **발송본 보호 불변식 유지**: `autoSummarize` 내부의 `emailedAt != null` 가드는 그대로 — 이미 발송된 dailies는 여전히 덮어쓰기로부터 안전. 지난 주 stale daily는 기존 freshness 경로에서 평소대로 재요약되며, **무조건 재요약하는 루프만** 좁아짐.\n- **monthly 요약**: 매 실행마다 hot 상태로 유지하지 않음. 월말 또는 필요 시 skill 경로(`circle --rerun YYYY-MM-DD`)에서 갱신.\n\n#### 변경 파일\n\n- `src/core/circle.ts` — `runCircle`/`circleJson` 범위 축소, `datesInCurrentMonthUpToToday` 삭제, 로그·주석 수정\n- `src/cli.ts` — 버전 0.8.9\n- `package.json` — `0.8.8` → `0.8.9`\n- `README.md` / `README.ko.md` — changelog + 본문 교차 참조 업데이트\n\n#### 호환성\n\n- DB 스키마 변경 없음 (여전히 v4).\n- CLI 플래그 변경 없음 — 기본 동작의 범위만 좁아짐.\n- `outd` / `outw` / `outm` 동작 변경 없음 — force 타입은 자체 경로로 트리거됨.\n- 특정 과거 daily를 다시 요약해야 하면 `circle --rerun YYYY-MM-DD`가 명시적 escape hatch로 그대로 사용 가능.\n\n---\n\n### v0.8.8 (2026-04-17) — 휴리스틱 주간/월간 baseline 제거 + DB atomic write + 렌더러 버그 수정\n\n#### 하이라이트\n\n- **휴리스틱 주간/월간 baseline 완전 제거.** 텍스트-조합 기반 경로 (`summarizeRangeInto`, `autoSummarizeWeekly`, `autoSummarizeMonthly`, `mergeSummariesByTitle`, 그리고 daily overview의 `topics.join(\", \")` 폴백) 전부 삭제. 이제 주간/월간 보고서는 **오직** skill 경로 `circle --save --type \u003cweekly|monthly\u003e`로만 생성됩니다. CLI가 outcomes·flows를 이어붙여 임의로 요약을 만들어내는 일은 없음.\n- **렌더러 버그 수정: 주간/월간이 월요일/1일의 하루치만 렌더되던 문제.** 이전 `renderDailyEmail`은 weekly/monthly에서도 `getGatherReportByDate(date)`를 호출했는데, `date`가 월요일/1일이라 그 하루치 gather만 가져와 세션으로 씀 — 주간/월간 row에 실제로 쌓여 있던 통합 세션들은 렌더에서 배제됐음. 이제 `gatherReport` 조회는 `reportType === \"daily\"`일 때만 수행.\n- **Atomic DB write.** `SqlJsAdapter.save()`가 `writeFile(dbPath, encrypted)`을 사용해 truncate-then-write로 쓰고 있었고, 쓰는 도중 프로세스가 죽으면 DB 파일이 0바이트로 남아 이후 모든 sincenety 명령에서 복호화 실패(실제 2026-04-17 작업 중 발생해 DB와 config를 전부 날림). 이제 `dbPath.tmp.\u003cpid\u003e`로 쓰고 `rename`으로 교체 — 같은 파일시스템이면 atomic.\n- **최초 실행 백필 90일 → 7일.** `determineRange`가 체크포인트 없을 때 기본 90일을 돌리던 것을 7일로 축소. 신규 설치/복구 직후 3개월치 Workers AI 호출이 발생하는 낭비 제거.\n- **`out*`: 비어 있는 weekly/monthly에 명확한 에러.** `outw`/`outm`이 `renderDailyEmail`가 null을 돌려주면 조용히 \"skip\"하던 것을 **에러**로 승격. 메시지: `weekly report row for \u003cdate\u003e not found. Run /sincenety in Claude Code to generate a high-quality summary first.` skill 계약을 명시적으로 만듦.\n- **매 실행 시 이번 주/달 daily 강제 재요약.** 사용자 요구에 따라 `runCircle`은 이번 주(월~오늘) + 이번 달(1일~오늘)을 강제 요약 대상에 포함 (freshness skip 우회, `emailedAt != null` daily는 여전히 보호). `circle --json`도 이 범위를 항상 포함하므로 skill이 언제든 최신 daily를 기반으로 주간/월간 요약을 다시 만들 수 있음.\n\n#### 제거된 심볼 / 설정\n\n- `src/core/circle.ts`: `summarizeRangeInto`, `autoSummarizeWeekly`, `autoSummarizeMonthly`, `mergeSummariesByTitle`, `normalizeTitle`(머지 제거 후 미사용), `MergedSummary`(→ `SessionSummary`로 개명).\n- `src/core/out.ts`: `PIPELINE_MODES`, `PipelineMode`, `PIPELINE_MODE_CONFIG_KEY`, `isPipelineMode`, `resolvePipelineMode`. `OutOptions.mode` 필드 삭제.\n- `src/cli.ts`: `parseModeFlag`, `out`/`outd`/`outw`/`outm`의 `--mode` 플래그, `config`의 `--pipeline-mode`(이제 deprecation 경고만 출력하고 아무 동작도 하지 않음).\n- `pipeline_mode` config 키는 더 이상 어디서도 읽히지 않음. 신규 설치는 이 키를 갖지 않음.\n\n#### out* 에러 계약 (v0.8.8+)\n\n주간/월간 row가 없거나 `summaryJson`이 비어 있을 때 `runOut`은 `skipped`가 아닌 **`error` 엔트리**를 기록하고 `result.errors`를 올립니다. 에러 메시지는 사용자에게 \"`/sincenety`로 재요약 먼저 수행하라\"고 안내. cron은 기존대로 `result.errors \u003e 0` 시 exit code 1로 감지. daily는 기존 `skipped` 경로 유지 (활동 없는 날은 에러가 아님).\n\n#### circle 동작 변경\n\n- `runCircle`: `forcedWeekMonth = (이번 주 ∪ 이번 달) ∩ (gather 존재)`를 계산하여 `allChanged`에 합침 + `autoSummarize`의 신규 `forceDates` 파라미터로 전달. 이 집합의 freshness 체크를 우회해서 매번 재요약하되, `emailedAt != null` daily는 계속 보호.\n- `circleJson`: 같은 범위 확장 적용 — skill 클라이언트에서는 gather 변경이 없어도 이번 주/달이 항상 \"처리 대상 날짜\"로 보임.\n- Daily `overview`가 Cloudflare `generateOverview` 실패 시 `\"\u003cdate\u003e 작업: topic1, topic2, ...\"`로 폴백하던 것 제거. AI가 실패하면 `overview`는 null 유지 — 렌더러가 null을 무리 없이 처리함. v0.8.6의 \"휴리스틱 요약 절대 금지\" 규칙과 일관.\n\n#### 렌더러 수정\n\n`src/email/renderer.ts`:\n\n```diff\n- const gatherReport = await storage.getGatherReportByDate(date);\n+ // weekly/monthly는 기간 rollup이므로 per-day gather를 쓰면 안 된다\n+ // (date가 월요일/1일이어서 그 하루치 gather만 잡혀 세션이 과소 표시됨).\n+ const gatherReport =\n+   reportType === \"daily\"\n+     ? await storage.getGatherReportByDate(date)\n+     : null;\n```\n\n이 버그가 금요일 주간보고 제목에서 `1 sessions, 4msg, 0Ktok`이 찍히던 직접 원인. 주간 row에 8개 프로젝트 통합 요약이 들어 있어도 월요일 하루치 gather(1세션 4메시지)만 렌더되고 있었음.\n\n#### Atomic DB 쓰기\n\n`src/storage/sqljs-adapter.ts`:\n\n```diff\n  private async save(): Promise\u003cvoid\u003e {\n    if (!this.db) return;\n    const data = this.db.export();\n    const encrypted = encrypt(Buffer.from(data), this.encryptionKey);\n-   await writeFile(this.dbPath, encrypted, { mode: 0o600 });\n+   // Atomic write: tmp → rename. writeFile()은 truncate-then-write라\n+   // 쓰기 중 크래시 시 DB가 0바이트로 날아감. rename은 같은 FS에서 atomic.\n+   const tmpPath = `${this.dbPath}.tmp.${process.pid}`;\n+   await writeFile(tmpPath, encrypted, { mode: 0o600 });\n+   await rename(tmpPath, this.dbPath);\n  }\n```\n\n재발 트리거: 2026-04-17 주간 재발송 중 send-path의 `save()`가 도중 끊겨 `~/.sincenety/sincenety.db`가 0바이트로 남음. 이후 모든 명령이 \"DB decryption failed\" 출력. config (SMTP 앱 비밀번호, D1 API 토큰 포함)을 다시 입력해야 했고, 7일치 gather/daily 데이터를 재수집·재요약해야 했음. 이 atomic-write 수정으로 동일 사고 차단.\n\n#### 최초 실행 백필 축소\n\n`src/core/air.ts:determineRange` — 체크포인트 row가 없을 때 기본 백필 범위가 7일 (이전 90일). 과거 기본값은 cold start 시 분기 전체 분량의 Workers AI 호출을 유발하던 낭비였음.\n\n#### SKILL.md\n\n- \"파이프라인 모드\" 섹션 재작성: full/smart 구분 제거 및 휴리스틱 baseline 삭제 명시.\n- `outw`/`outm` 실패 모드 섹션 추가 — 에러 문자열 원문과 조치 방법 기재.\n- `circle --json`이 항상 이번 주/달을 포함한다는 점 명시 — skill이 주간/월간 재요약의 기반 자료로 활용할 수 있음.\n\n#### 변경 파일\n\n- `src/core/circle.ts` — 휴리스틱 baseline 함수들 삭제, daily overview 폴백 제거, forceDates 추가, circleJson 범위 확장\n- `src/core/out.ts` — PipelineMode 기계장치 삭제, weekly/monthly 빈 row 에러 처리\n- `src/core/air.ts` — 최초 실행 백필 90일 → 7일\n- `src/cli.ts` — `--mode` / `--pipeline-mode` 플래그 제거, parseModeFlag 제거, 버전 bump\n- `src/email/renderer.ts` — gather 조회를 `reportType === \"daily\"`로 게이팅\n- `src/storage/sqljs-adapter.ts` — tmp+rename 기반 atomic write 적용\n- `src/skill/SKILL.md` — 파이프라인/주간/월간 섹션 재작성\n- `package.json` — 0.8.7 → 0.8.8\n\n---\n\n### v0.8.7 (2026-04-16) — 수정: 당일 새 세션 추가 시 autoSummarize가 재요약하지 않던 버그\n\n#### 버그\n\n- **`autoSummarize`가 `daily_reports`에 `summaryJson`이 이미 있으면 재요약을 건너뛰는 문제.** 오전 10시에 `sincenety` 실행(세션 A, B 요약 완료) 후 오후 3시에 다시 실행하면, air는 새 세션 C를 감지(data hash 변경)하여 `gather_reports`를 갱신하지만, `autoSummarize`는 기존 `summaryJson`을 보고 `continue` — 세션 C가 일일 요약에 포함되지 않음. 이메일에는 오전 시점의 세션만 표시.\n\n#### 근본 원인\n\n`circle.ts:autoSummarize`의 662-665행:\n\n```typescript\nconst existingReport = await storage.getDailyReport(date, \"daily\");\nif (existingReport?.summaryJson) continue;\n```\n\n`summaryJson`이 존재하면 \"이 날짜는 요약 완료\"로 가정했으나, `air`가 새 세션으로 `gather_reports`를 갱신하면서 `updatedAt`이 갱신됨 — `daily_reports`는 **stale** 상태. `getDailyReportFreshness` 인프라가 이미 이를 감지하고 있었지만 `autoSummarize`가 이를 참조하지 않았음.\n\n#### 수정\n\n스킵 로직이 이제 freshness를 확인:\n\n1. **발송 완료** (`emailedAt != null`) → 항상 스킵 (발송본 보호)\n2. **최신** (gather의 `updatedAt` ≤ daily의 `createdAt`) → 스킵 (새 데이터 없음)\n3. **Stale** (gather가 daily 이후 갱신됨) → **재요약 수행** + `♻️ re-summarizing` 로그\n\nv0.8.4에서 도입된 기존 `storage.getDailyReportFreshness()` 메서드를 재활용 (이전에는 `out.ts`의 경고 로그에서만 사용).\n\n#### 변경 파일\n\n- **`src/core/circle.ts`**: `autoSummarize` — 단순 `summaryJson` 존재 체크를 freshness 기반 로직으로 교체\n- **`package.json`**: 버전 0.8.6 → 0.8.7\n- **`src/cli.ts`**: 버전 문자열 갱신\n\n---\n\n### v0.8.6 (2026-04-16) — 휴리스틱 요약 fallback 완전 제거 + AI 필수 파이프라인 가드 + 프롬프트 강화\n\n#### 하이라이트\n\n- **휴리스틱 요약 fallback 완전 제거.** `src/core/summarizer.ts`의 `summarizeHeuristic` 함수(정규식으로 입력/출력 문장을 잘라 이어붙이는 로직)를 **삭제**. \"AI 요약이 안 돌고 입력 텍스트가 그대로 정리되어 나간다\"는 오랜 증상의 근본 원인이었음. AI provider가 사용 불가일 때 silent로 호출되어 사용자 프롬프트를 베낀 가짜 요약을 만들고 있었음.\n- **파이프라인 전역 AI 필수 가드.** `assertAiReadyForCliPipeline()` 신규 가드가 `runCircle` 진입과 `autoSummarize` 진입에서 호출됨. `ai_provider`가 유효한 자격증명을 가진 `cloudflare` 또는 `anthropic`이 아니면 즉시 명확한 한글 에러 + `process.exit(1)`(cron 감지 가능)로 sincenety 전체 프로세스 중단. **이메일 발송 없음, 거짓 요약 저장 없음.**\n- **`ai_provider=claude-code`는 CLI에서 금지.** 이 값은 `/sincenety` 슬래시 명령(Claude Code가 외부에서 직접 요약을 만들어 `circle --save`로 저장하는 흐름) 안에서만 유효. CLI/cron으로 `sincenety` 직접 실행 시 슬래시 명령 전용임을 알리는 메시지와 함께 throw.\n- **양 AI 경로 프롬프트 강화.** Cloudflare Workers AI와 Anthropic 프롬프트 모두 사용자 발화의 출력 등장을 명시적으로 금지. 프롬프트 구조를 \"사용자 의도(맥락 전용)\"와 \"어시스턴트 수행/산출물(요약 대상)\"로 분리하고, 무엇이 만들어지고 변경됐는지를 3인칭 관찰자 시점으로 기술하도록 지시.\n\n#### \"AI 요약 안 돌던\" 증상의 근본 원인\n\nv0.8.6 이전 `src/core/summarizer.ts:279`의 분기는 `cloudflare`와 `anthropic` provider만 처리하고 `claude-code`와 미설정 상태에선 **휴리스틱으로 fallthrough**. 거기에 `summarizer.ts:141`의 `catch {}`가 Anthropic API 모든 에러를 silent로 삼킴 — provider 설정 오류, 네트워크 에러, 인증 실패 어느 것이든 조용히 정규식 기반 입력-텍스트 echo로 격하됨. 사용자 입장: \"AI 설정한 것 같은데 이메일은 내가 입력한 글이 정리만 된 것처럼 보임.\" 실제로 그게 일어나고 있었음 — 정규식이 `userInput`의 첫 문장들을 잘라내 화살표로 이어 붙이고 있었음.\n\n두 번째 오염 경로: `src/core/gatherer.ts`도 매 갈무리마다 `summarizeSessions`를 호출해 세션 title을 채우고 있었음. 실제 요약은 이후 `autoSummarize`에서 따로 일어나므로 이 호출은 불필요했고 휴리스틱을 한 번 더 흘려넣고 있었음. 이 호출도 제거.\n\n#### 주요 변경\n\n- **`src/core/summarizer.ts`**:\n  - **삭제**: `summarizeHeuristic()` 함수(정규식 추출 로직 약 90줄).\n  - **신규**: `AiUnavailableError` 예외 클래스 — 호출자가 잡아 파이프라인을 중단할 수 있게.\n  - **`summarizeSession()`** 반환 타입을 `Promise\u003cSessionSummary\u003e`(non-nullable)로 변경, 어떤 실패에서도 throw.\n  - **`summarizeWithClaude`의 silent `catch {}` 제거** — Anthropic SDK 에러(인증, rate limit, 네트워크) 그대로 전파. 빈 응답·JSON 파싱 실패도 동일.\n  - **`claude-code` provider는 명시적으로 throw** — 슬래시 명령 전용임을 메시지로 안내.\n  - **Heuristic / 미설정 provider도 명시적으로 throw** — 설정 가이드 hint 포함.\n  - 프롬프트 구조 변경: 턴 포맷이 `[턴 N] 사용자 의도(맥락): ... / 어시스턴트 수행/산출물: ...` — 의도와 행위를 분리. 출력 규칙이 사용자 발화 인용 금지, 3인칭 시점을 명시.\n\n- **`src/core/ai-provider.ts`**:\n  - **신규**: `assertAiReadyForCliPipeline(storage)` — 파이프라인 진입 체크의 단일 진실 공급원. `claude-code`(CLI 컨텍스트), `heuristic`(provider 없음), 자격증명 없는 `cloudflare`/`anthropic`을 명확히 거부.\n\n- **`src/core/circle.ts:autoSummarize`**:\n  - 진입 시 가드 호출 — AI 준비 안 됐으면 어떤 DB 읽기도 시작하기 전에 throw.\n  - 세션별 AI 실패가 더 이상 날짜 단위 `try/catch`에 묻히지 않음. catch 블록 자체를 제거. AI 실패가 호출자로 전파되어 그 `autoSummarize` 호출 전체가 중단됨. **부분적 요약은 절대 `daily_reports`에 쓰지 않음.**\n  - Cloudflare `cfSummarize`의 null 반환도 실패로 간주하여 throw 승격(이전: silent skip).\n  - 크로스 디바이스 D1 pull의 `try/catch`는 유지(네트워크 flakiness는 예상되고 요약 정확성과 직교적), 다만 silent `catch` 블록들이 이제 실제 에러 메시지를 경고로 출력.\n\n- **`src/core/circle.ts:runCircle`**:\n  - `autoSummarize` 호출 직전에 `assertAiReadyForCliPipeline(storage)` 호출(CLI 경로 한정 — `--json`/`--save`/`--skipAutoSummarize` 경로는 슬래시 명령 흐름 전용이라 우회).\n\n- **`src/core/gatherer.ts`**:\n  - `summarizeSessions` 호출 **제거**. `gather_reports` 행은 raw 세션 메타데이터만 저장(XML 태그 제거된 `s.title ?? s.summary`). 휴리스틱이 채우던 `wrapUp` 필드도 제거. AI 생성 wrapUp은 `daily_reports.summary_json`에서만 살아있도록 정리.\n\n- **`src/cloud/cf-ai.ts`**:\n  - 프롬프트 재작성: `summarizer.ts`와 동일하게 의도/행위 분리, 시스템 프롬프트가 5개 출력 규칙 명시(사용자 인용 금지, 3인칭, 의도+산출물 통합, 인사·중간보고 무시, JSON만 출력).\n  - **silent `catch { return null }` 제거** — HTTP 에러, content 누락, JSON 파싱 실패 전부 설명적 메시지로 throw하여 `circle.autoSummarize`가 파이프라인을 중단할 수 있게.\n\n#### 테스트 변경\n\n- **`tests/cf-ai.test.ts`**: \"should handle API errors gracefully\" 테스트 이름 변경 + 재작성 — 이제 `rejects.toThrow(/Workers AI HTTP 401/)`. 새 계약 반영: silent null 반환 금지.\n- **`tests/circle.test.ts`**: 기존 `runCircle — summaryErrors propagation` 4개 테스트가 가드를 통과하도록 `ai_provider=cloudflare` + 더미 D1 자격증명 시딩 추가. 신규 2개 테스트:\n  - `AI provider 미설정 상태에서 runCircle이 throw — 전체 파이프라인 중단`\n  - `ai_provider=claude-code는 CLI 경로에서 throw — 슬래시 명령 전용임을 알림`\n- **173/173 테스트 통과** (이전 171 → 가드 테스트 +2, 제거 0).\n\n#### 검증\n\n1. **사용자 증상의 포렌식 재현**: `daily_reports`의 2026-04-14 행 검사 결과 `topic = \"오늘 cli 환경에서 실행한거 skip 되었던데, 내 홈 위치 실행했어(~/), 왜 sk…\"`, `flow = \"오늘 cli 환경에서 실행한거 skip 되었던데, 내… → cli 에서 어제 날짜기준 정리하려면 어떻게 명령어 …\"` — 휴리스틱이 사용자 프롬프트 텍스트를 그대로 echo한 것을 그대로 확인. 근본 원인 확정.\n2. **Cloudflare AI smoke 테스트**(격리): 위에서 echo됐던 사용자 입력 그대로를 직접 `summarizeSession`에 넣어 호출 → `topic: \"skill 자동 호출 문제 해결\"`, outcome은 실제 코드 변경(파일 경로, 버전 범프, 설치 로직)을 3인칭으로 기술. 사용자 프롬프트 문자 0건 출현.\n3. **가드 테스트**(격리): `ai_provider=claude-code`로 `summarizeSession` 호출 → `AiUnavailableError`가 슬래시 명령 안내 메시지와 함께 throw 확인.\n4. **E2E CLI 테스트**: `node dist/cli.js out`을 `ai_provider=claude-code` 상태로 실행 → 명확한 한글 에러 메시지 출력, 이메일 0건 발송, exit code 1(직접 확인).\n5. **이전 행 강제 재요약**: 2026-04-14(6 세션, 머지 후 5개)와 2026-04-15(1 세션)를 `autoSummarize`로 직접 재생성. 모든 세션 topic/flow가 행위 명사구로 정리되어 사용자 프롬프트 흔적 0. overview는 그날의 작업을 통합적으로 요약.\n\n#### 마이그레이션 영향\n\n- **`ai_provider=claude-code`인데 cron/CLI에서 `sincenety` 실행하던 사용자**: cron이 exit 1 + 명확한 메시지로 실패하기 시작함. `sincenety config --ai-provider cloudflare`(기존 D1 토큰 사용) 또는 `sincenety config --ai-provider anthropic` + `ANTHROPIC_API_KEY` 환경변수 설정으로 전환.\n- **AI 미설정 사용자**: 동일 — 휴리스틱 가짜 이메일 보내느니 파이프라인이 거부. cloudflare 또는 anthropic 설정.\n- **휴리스틱이 만든 기존 `daily_reports` 행**: 자동 교정되지 않음(`emailedAt != null` 가드가 덮어쓰기를 막음). 수동 재생성 절차: 해당 행의 `summaryJson`과 `emailedAt`을 NULL로 초기화한 뒤 `circle` 재실행 또는 `autoSummarize` 직접 호출.\n\n---\n\n### v0.8.5 (2026-04-15) — `npm install -g` 시 Claude Code skill 자동 설치\n\n#### 하이라이트\n\n- **\"다른 기기에서 `/sincenety`가 슬래시 명령 목록에 안 뜨는 문제\" 수정**: v0.8.5 이전에는 `npm install -g sincenety`를 해도 CLI 바이너리만 설치될 뿐, Claude Code가 slash command를 인식하는 데 필요한 `~/.claude/skills/sincenety/SKILL.md` 파일은 **전혀 생성되지 않았음**. 원개발 기기에서만 과거 수동 복사로 존재했던 것이고, 신규 설치 기기에서는 `/sincenety`가 목록에 나타날 수 없는 구조였음.\n- **근본 원인 (두 개의 버그가 겹침)**:\n  1. `package.json`의 `files` 화이트리스트가 `[\"dist\"]`뿐이어서, **`src/skill/SKILL.md`가 npm 패키지 tarball에 아예 포함되지 않음**. postinstall이 복사하려 해도 원본 파일 자체가 설치된 경로에 존재하지 않는 상태.\n  2. `src/postinstall.ts`에 **skill 복사 로직이 0줄**. grep으로 `skill|SKILL|.claude` 패턴 매치 0건 확인. 기존 postinstall은 D1/SMTP 셋업 위저드였고, 비-TTY 환경에서는 한 줄 안내만 출력하고 즉시 종료 — 어떤 환경에서도 skill 등록 단계가 실행된 적 없음.\n\n#### 주요 변경\n\n- **`package.json`의 `files`**: `[\"dist\", \"src/skill/SKILL.md\"]` — 발행되는 npm tarball에 skill 정의 파일을 포함시켜 설치자에게 전달.\n- **`src/postinstall.ts`에 `installSkill()` 함수 신규 추가**: `import.meta.url` 기반으로 패키지 내 `SKILL.md` 경로를 2개 후보(npm 배포본 레이아웃 `\u003cpkgRoot\u003e/src/skill/SKILL.md`, 로컬 dev 레이아웃)로 탐색 → `~/.claude/skills/sincenety/`를 `mkdirSync({recursive: true})`로 생성 → `copyFileSync`로 복사. 전체 try/catch로 감싸서 복사 실패 시에도 CLI 설치 자체는 중단되지 않고 경고만 출력.\n- **호출 위치**: `main()` 최상단, **TTY 체크 이전**에 호출. 기존 postinstall은 비-TTY에서 즉시 return하는 구조였는데 이 위치에서 skill을 설치했다면 CI/Docker/스크립트 설치 시 skill 등록이 누락됐을 것. 사용자 입력이 필요 없는 작업이므로 TTY 여부와 무관하게 항상 실행되도록 배치.\n\n#### 검증\n\n- 비-TTY 드라이런: `node -e \"process.stdin.isTTY=false; import('./dist/postinstall.js')\"` → `✓ Claude Code skill installed: /Users/.../SKILL.md` 출력, 실제 파일 10,444 bytes 확인.\n- TypeScript 빌드 클린 (`tsc` 출력 0).\n- 기존 테스트 영향 없음 (gatherer/summarizer/render 경로 로직 무변경).\n\n#### 마이그레이션 참고\n\nv0.8.4에서 업그레이드하는 원개발 기기는 SKILL.md가 동일 내용으로 덮어써짐. `/sincenety`가 누락되어 있던 신규 기기에서는 설치 후 Claude Code 재시작 시 slash command 목록에 표시됨.\n\n---\n\n### v0.1 (2026-04-07) — MVP\n\n- `history.jsonl` 기반 소급 갈무리\n- sql.js 암호화 DB (AES-256-GCM)\n- 세션 그룹핑 (sessionId + project)\n- 터미널 리포트\n- Claude Code Skill 등록 (`/sincenety`)\n\n### v0.1.1 (2026-04-07) — 보안 강화\n\n보안 감사 결과 CRITICAL 2개 + HIGH 3개 발견, 전부 수정:\n- 랜덤 salt 생성/저장 (`~/.sincenety/sincenety.salt`)\n- DB/salt 파일 권한 0600, 디렉토리 0700\n- 복호화 실패 시 에러 메시지 (무음 DB 교체 제거)\n- CLI 입력 검증 (날짜, 경로, 빈 값)\n- MariaDB URI 보안 (환경변수 참조)\n\n### v0.2 (2026-04-07) — 풍부한 작업 기록 + 이메일 + 스케줄링\n\n데이터소스 전환: `history.jsonl` → `~/.claude/projects/[project]/[sessionId].jsonl`\n\n- **토큰 추적**: 메시지별 input/output/cache 토큰 합산\n- **모델 추적**: assistant 응답에서 사용 모델 추출\n- **정밀 타이밍**: ISO 8601 타임스탬프 기반 (밀리초 정밀도)\n- **DB 스키마 v2**: sessions 14개 컬럼 추가, gather_reports/config 테이블\n- **자동 마이그레이션**: v1 → v2 ALTER TABLE ADD COLUMN\n- **마크다운 리포트**: 토큰/모델 포함 풍부한 리포트\n- **이메일 발송**: nodemailer + Gmail SMTP\n- **HTML 이메일 템플릿**: Bright 컬러코딩 대시보드\n- **범위 선택**: Global (전체) / Project (특정 프로젝트) 모드\n- **postinstall 셋업 위저드**: `npm install -g` 시 대화형 3단계 설정\n- **날짜 지정 보고서**: `--date yyyyMMdd`로 out/outd/outw/outm 특정 날짜 발송\n\n### v0.2.1 (2026-04-07) — 이메일 AI 요약 통합\n\n- **이메일에 AI 요약 반영**: `daily_reports`의 AI 요약을 이메일 템플릿에 매핑\n- **일일보고 overview**: 이메일 최상단에 하루 종합 요약 섹션 추가\n- **Gmail 클립 방지**: actions를 세션당 최대 5건으로 제한\n- **제목 개선**: \"작업 갈무리\" → \"일일보고\"로 변경\n\n### v0.3 (2026-04-07) — AI 요약 일일보고 + 주간/월간 보고\n\n- **기본 갈무리 범위 변경**: 항상 오늘 00:00부터 (upsert로 중복 방지)\n- **대화 턴 수집**: `session-jsonl.ts`에서 사용자 입력 + 어시스턴트 응답 쌍(`conversationTurns`) 추출\n- **터미널 테이블 출력**: 요약 테이블 + 세션 상세 테이블 (유니코드 박스 드로잉)\n- **AI 요약 아키텍처**: `--json` 플래그로 대화 턴 포함 구조화 JSON 출력\n- **summarizer.ts**: Claude API 요약 + 휴리스틱 fallback\n- **일일보고 시스템**: `save-daily`, `report` (일일/주간/월간)\n- **DB 스키마 v3**: `daily_reports` 테이블 추가\n\n### v0.3.0 (2026-04-07) — air/circle 파이프라인, DB v4\n\nCLI를 7개 명령에서 3단계 파이프라인으로 전면 재구성:\n\n- **`air` 명령**: 날짜별 갈무리 (자정 경계, startedAt 기준), checkpoint 기반 자동 백필, data hash 변경 감지\n- **`circle` 명령**: 내부적으로 air 실행 후 LLM 요약 연동, `--json`/`--save`/`--type` 옵션, 자동 finalization (전날/전주/전월 확정)\n- **`config` 강화**: 인자 없이 실행 시 ANSI 설정 상태 테이블, `--vacation` 휴가 등록, `--provider`/`--resend-key` 이메일 provider 선택\n- **DB 스키마 v4**: gather_reports에 report_date/data_hash/updated_at 추가, daily_reports에 status/progress_label/data_hash 추가, `email_logs`/`vacations` 테이블 신규\n- **v3→v4 자동 마이그레이션**\n- **테스트 50개**: encryption 26 + migration-v4 7 + air 7 + circle 10\n\n### v0.3.1 (2026-04-07) — Plan 2: out/outd/outw/outm 스마트 발신\n\n- **`out` 명령**: 요일 기반 스마트 발신 (일일 항상, 금요일 +주간, 월말 +월간)\n- **미발송 캐치업**: 금요일 놓치면 월요일에 주간보고 자동 발송\n- **4가지 email provider**: Gmail MCP / Resend / Gmail SMTP / Custom SMTP\n- **`outd`/`outw`/`outm`**: 보고 타입별 강제 발신 명령\n- **`--preview`**: 발송 없이 미리보기\n- **`--render-only`**: HTML JSON 출력 (Gmail MCP 연동용)\n- **`--history`**: 발송 내역 조회\n- **`src/email/renderer.ts`**: HTML 이메일 렌더러\n- **`src/email/resend.ts`**: Resend API provider\n- **`src/email/provider.ts`**: provider 추상화 레이어\n- **`src/core/out.ts`**: out 명령 핵심 로직\n- **테스트 78개**: 기존 50 + out 28개 추가\n\n### v0.3.2 (2026-04-07) — Plan 3: 휴가 관리, 설정 위저드, Gmail MCP\n\n- **휴가 관리**: Google Calendar 자동 감지 (SKILL.md), CLI 수동 등록 (`config --vacation`)\n- **휴가 키워드 감지**: 한국어+영어 (휴가/vacation/연차/PTO/병가/sick/반차/half-day)\n- **보고서 휴가 연동**: [휴가] 라벨 표시, `out` 휴가일 자동 스킵\n- **`config --setup` 위저드**: 대화형 3선택 (Gmail SMTP / Resend / Custom SMTP), 연결 테스트\n- **Gmail MCP 연동**: `gmail_create_draft`로 zero-config 이메일, `out --render-only` MCP 경로\n- **`src/vacation/manager.ts`**: 휴가 CRUD (등록/조회/삭제/확인)\n- **`src/vacation/detector.ts`**: 휴가 키워드 감지\n- **`src/config/setup-wizard.ts`**: 대화형 설정 위저드\n- **테스트 91개**: 기존 78 + vacation 13개 추가\n\n### v0.4.0 (2026-04-07) — Plan 4: Cloudflare D1 클라우드 동기화\n\n- **`sync` 명령**: D1 push/pull/status/init\n- **멀티머신 통합**: Cloudflare D1 REST API로 여러 머신의 데이터 중앙 집약\n- **로컬 우선**: 암호화된 로컬 DB가 source of truth, D1은 집약 계층\n- **자동 동기화**: `out` 완료 후 자동 sync\n- **공유 설정**: SMTP 등 설정을 D1에 저장, `sync --pull-config`로 새 머신 즉시 설정\n- **`src/cloud/d1-client.ts`**: Cloudflare D1 REST API 클라이언트\n- **`src/cloud/d1-schema.ts`**: D1 스키마 정의 및 마이그레이션\n- **`src/cloud/sync.ts`**: 동기화 핵심 로직\n- **테스트 108개**: 기존 91 + D1/sync 17개 추가\n\n### v0.5.0 (2026-04-07) — Cloudflare Workers AI 요약 엔진\n\n- **Cloudflare Workers AI 요약 엔진 (Qwen3-30B)**: 한국어 텍스트 요약 특화, D1 토큰만으로 자동 활성화\n- **Token-only D1 auto-setup**: account/database 자동 감지/생성, 토큰 하나면 나머지 전부 자동\n- **하드웨어 기반 자동 machine ID**: macOS (IOPlatformUUID) / Linux (/etc/machine-id) / Windows (MachineGuid)\n- **machines 레지스트리 테이블**: D1에 머신 정보 자동 등록\n- **4단계 요약 우선순위**: Claude Code (SKILL.md) → Workers AI → Claude API → 휴리스틱 fallback\n- **`src/cloud/cf-ai.ts`**: Workers AI 클라이언트\n- **`src/cloud/d1-auto-setup.ts`**: Token-only 자동 설정\n- **`src/util/machine-id.ts`**: 크로스플랫폼 하드웨어 ID 감지\n- **테스트 116개**: 기존 108 + cf-ai/machine-id 8개 추가\n\n### v0.8.4 (2026-04-11) — 파이프라인 모드 스위치 + 주간/월간 베이스라인 자동 생성 + 묵음 실패 차단\n\n#### 핵심 요약\n\n- **주간/월간 베이스라인 자동 생성**: `out`/`outd`/`outw`/`outm` 실행 시마다 이번 주 weekly와 이번 달 monthly row를 DB에 자동 upsert. 이전에는 `daily_reports`에만 row가 있고 weekly/monthly row는 생성되지 않아서, `outw`/`outm`이 빈 결과를 내는 구조적 공백이 있었음. 이미 발송된 보고서(`emailedAt != null`)는 보호되어 절대 덮어쓰지 않음.\n- **`--mode=full|smart` 파이프라인 스위치**: 새 CLI 플래그와 `config --pipeline-mode` 설정 추가. `full`(기본)은 매 실행마다 weekly/monthly 베이스라인을 재생성, `smart`는 v0.8.3까지의 동작을 유지(토큰 절약, 요일 트리거만 — 금요일 weekly, 말일 monthly). 모드 결정 우선순위는 CLI 옵션 \u003e config 값 \u003e 기본값 `full`.\n- **묵음 실패 차단**: 이전에는 `console.warn`만 찍고 파이프라인이 계속 진행되던 실패 경로 여러 곳을 구조화된 에러 채널로 표면화. CLI exit code로 cron 환경에서 실패를 감지할 수 있게 됨.\n\n#### 핵심 변경\n\n- **`autoSummarizeWeekly` / `autoSummarizeMonthly`** (`src/core/circle.ts`): 이번 주(월~일) 또는 이번 달(1일~말일)의 `daily_reports`를 모아 `summaryJson`의 세션을 flatten한 뒤, `mergeSummariesByTitle`로 프로젝트 단위 통합을 수행해 weekly/monthly row를 upsert. 두 함수는 공통 private helper `summarizeRangeInto`가 집계/기간 경계 계산/upsert 로직을 담당.\n- **`PipelineMode` 타입 중앙화** (`src/core/out.ts`): `PIPELINE_MODES` 상수 배열, `PipelineMode` 리터럴 유니언 타입, `isPipelineMode()` 런타임 타입가드, `PIPELINE_MODE_CONFIG_KEY` 상수를 한 곳에 export. 기존에 `out.ts`·`circle.ts`·`cli.ts` 네 곳에 흩어져 있던 `\"smart\" | \"full\"` 리터럴 중복을 제거.\n- **`resolvePipelineMode()`**: 우선순위 해석 순수 함수 — explicit option \u003e config value \u003e 기본값 `\"full\"`. 유효하지 않은 `configured` 값(오타, 구버전 데이터 등)은 조용히 `\"full\"`로 폴백 — `config --pipeline-mode` 쓰기 시점에 검증이 걸리므로 안전.\n- **`CircleResult.summaryErrors`**: auto-summary 실패를 타입별로 기록하는 신규 필드 — `{type: \"weekly\" | \"monthly\"; error: string}[]`.\n- **`collectUnrecordedSummaryErrors()`**: `out.ts`의 순수 헬퍼 — `circleResult.summaryErrors`를 `OutResultEntry` error로 승격시키되, 렌더 루프에서 이미 기록한 항목은 중복 제거.\n- **`runOut` 구조 재정렬**: `runCircle` 직후에 `summaryErrors`를 글로벌 error entry로 **먼저** 수집. 휴가 체크/force/reportTypes 분기 이전에 기록되므로, 휴가일 early return으로 빠지거나 실패 타입이 `reportTypes`에 포함되지 않아도 실패가 반드시 반영됨.\n- **CLI exit code 전파**: `out`/`outd`/`outw`/`outm`이 `result.errors \u003e 0`일 때 `process.exitCode = 1` 설정. `process.exit(1)`이 아닌 `exitCode`를 쓰는 이유는 `finally` 블록의 `storage.close()`가 끝까지 실행되어 sql.js WASM DB flush 안정성이 확보되기 때문.\n\n#### 버그 수정\n\n- **`config --pipeline-mode smrt` 타이포가 exit 0으로 조용히 끝나던 문제**: 검증 경로가 `console.log` 후 fall-through로 종료해서 스크립트/CI가 오타를 감지할 수 없었음. 이제 `out --mode` 검증과 동일하게 `console.error` + `process.exit(1)`로 실패.\n- **`emailedAt === 0` falsy 가드**: 기존 가드 `if (existing?.emailedAt) return false`는 `emailedAt === 0`을 falsy로 판정해 \"미발송\"으로 분류하므로, 발송된 row를 덮어쓸 수 있는 엣지 케이스가 있었음. `Date.now()`는 0을 반환할 일이 없지만 수동 DB 삽입이나 버그로 이 값이 들어올 수 있음. 명시적 null 비교 `if (existing \u0026\u0026 existing.emailedAt != null) return false`로 교체.\n- **JSON.parse 묵음 드롭**: `summarizeRangeInto`의 `try { JSON.parse(...) } catch {}` 블록이 모든 예외를 흔적 없이 삼키던 문제. 손상된 daily row 하나가 해당 날짜의 세션을 조용히 누락시켜 집계 언더카운트가 발생할 수 있었음. 이제 `SyntaxError`만 catch하여 실패한 `reportDate`를 명시한 `console.warn`을 내고, 그 외 예외 클래스(`TypeError` 등)는 re-throw.\n- **dead `\"finalized\"` 분기 제거**: `summarizeRangeInto`에 있던 `status = todayTs \u003c= periodTo ? \"in_progress\" : \"finalized\"` 분기가 두 호출자(`autoSummarizeWeekly`/`autoSummarizeMonthly`)가 `today`로부터 range를 파생하는 구조상 `today`는 항상 `[rangeFrom, rangeTo]` 안이라 `\"finalized\"` 경로가 절대 실행되지 않는 dead code였음. `\"in_progress\"`로 하드코딩하고 기간 종료 시점 전환은 기존처럼 `finalizePreviousReports`가 담당.\n\n#### 테스트 개선\n\n**171개 테스트 전부 통과** (기준선 151 → +20 신규). 모든 신규 테스트는 TDD red → green 원칙을 따름.\n\n- **`autoSummarizeWeekly` / `autoSummarizeMonthly`** — 8개: 이번 주/달 daily로부터 row 생성, 상태는 `in_progress`, 데이터 없을 때 스킵, 미발송 row upsert, 발송 row 보호(8개 필드 스냅샷 비교 — `summaryJson`/`overview`/`sessionCount`/`totalMessages`/`totalTokens`/`emailedAt`/`emailTo`/`createdAt`), `emailedAt === 0` falsy 가드.\n- **`summarizeRangeInto` JSON 손상 처리** — 3개: 손상된 JSON에서 `reportDate` 경고 후 나머지 daily는 정상 처리, 배열이 아닌 JSON(예: `\"null\"`, `\"{}\"`)은 스킵, 빈 `summaryJson` 문자열은 스킵.\n- **경계 케이스** — 5개: 오늘이 일요일(`getWeekBoundary`의 일요일 전용 분기 검증), 오늘이 월요일, 12월→1월 월 경계, 2028년 2월 윤년(2/29 포함), 2027년 2월 평년(3/1 제외).\n- **`runCircle` summaryErrors 전파** — 4개. Proxy로 감싼 throwing `StorageAdapter`를 주입하여 검증: weekly 실패가 monthly 진행을 막지 않음, monthly 실패가 weekly와 독립 기록, smart 모드는 호출 자체를 건너뛰므로 에러 없음, 정상 storage는 빈 `summaryErrors` 반환.\n- **`resolvePipelineMode`** — 7개: 기본값, explicit 옵션 override, config 폴백, 유효하지 않은 값 처리.\n- **`collectUnrecordedSummaryErrors`** — 7개: 빈 입력, weekly/monthly 승격, 기존 entry와의 중복 제거, 다중 실패, 에러 메시지 포맷 검증.\n- **\"발송 보호\" 테스트 강화**: 이전엔 `projectName === \"sent\"`와 `emailedAt`만 확인하던 테스트(aggregation이 실행돼도 이 두 값은 유지되므로 실제 보호를 증명 못 하던 false-confidence 테스트)를 before/after 스냅샷 비교로 바꿔 8개 필드 전부 불변 검증. 덮어쓰기가 실행되면 totals가 변할 만큼 큰 sentinel daily를 의도적으로 심어두고 감지.\n- **수동 fault injection 스모크 테스트**: Proxy로 throwing storage를 주입한 상태에서 `runOut`을 돌려 Gap B 수정을 확증 — 휴가일에 weekly 실패 시 `result.errors = 1` + exit 1, `force: weekly`에 monthly auto-summary 실패 시 orphan monthly 에러가 글로벌 entry로 기록됨.\n\n#### 문서 업데이트\n\n- **SKILL.md — \"파이프라인 모드 (v0.8.4+)\" 섹션 신설**: `full`/`smart` 모드 설명과 `emailedAt != null` 보호 규칙 명시.\n- **SKILL.md — \"주간/월간 보고 고품질 재요약\" 워크플로우**: 기존 \"워크플로우: 주간/월간 보고 생성\" 섹션을 4단계 흐름으로 재작성 — (1) `out` 실행으로 베이스라인 자동 생성, (2) `circle --json`으로 기간 데이터 조회/분석, (3) `circle --save --type weekly|monthly`로 재요약 덮어쓰기, (4) `outw`/`outm`으로 발송.\n\n#### 변경 파일\n\n`src/cli.ts` (+54), `src/core/circle.ts` (+203), `src/core/out.ts` (+109), `src/skill/SKILL.md` (+54), `tests/circle.test.ts` (+625), `tests/out.test.ts` (+110). 6개 파일 총 ~1143 insertions / ~12 deletions.\n\n### v0.8.3 (2026-04-09) — 프로젝트 단위 세션 통합으로 단순화\n\n- **세션 통합 로직 단순화**: 머지 기준이 \"프로젝트 내 동일 제목\" (`projectName::normalizedTitle`)에서 \"프로젝트 단위\" (`projectName`만)로 변경. 최종 결과 = 프로젝트당 하나의 항목, 세션 제목과 무관\n- **circle.ts**: `mergeSummariesByTitle()` 그룹핑 키를 `projectName::normalizedTitle`에서 `projectName`으로 변경\n- **merge-sessions.ts**: `mergeSessionsByTopic()` 그룹핑 키를 `projectName::normalizedTitle`에서 `projectName`으로 변경\n- **SKILL.md 업데이트 (양쪽 복사본)**: 기존 2-pass mergeGroup 통합 및 3-pass 프로젝트 통합을 제거하고 `projectName` 기준 단일 2-pass로 교체\n- **`--summarize` 경로**: 동일한 프로젝트 단위 통합 적용\n- **테스트**: 동일 프로젝트의 서로 다른 제목 세션도 머지되도록 기대값 업데이트\n\n### v0.8.2 (2026-04-09) — Circle 동일 제목 세션 통합 요약\n\n- **circle 동일 제목 세션 머지**: 같은 날짜 내 `projectName + normalizedTitle`이 동일한 세션이 여러 개 있으면, 개별 요약 후 통합 재요약을 생성. 각 세션의 outcome은 합산, flow는 `→`로 연결, significance는 가장 긴 것 채택, nextSteps는 마지막 세션 기준. 머지된 항목은 topic에 `(×N)` 표시\n- **양쪽 요약 경로 모두 적용**: `autoSummarize` (CLI 자동 요약 — Cloudflare AI / heuristic)와 SKILL.md 흐름 (Claude Code 직접 요약 — `circle --json`의 `mergeGroup` 힌트) 양쪽에서 동작\n- **`mergeGroup` 필드 추가**: `circle --json` 출력의 각 세션에 `mergeGroup` 필드 (`projectName::normalizedTitle`) 포함 — Claude Code가 SKILL.md 2단계에서 머지 대상을 식별\n- **SKILL.md 업데이트**: 2단계에 \"통합 재요약\" 지시 추가 — 개별 분석 후 같은 `mergeGroup` 세션을 합쳐서 통합 요약 생성\n- **신규 함수**: `circle.ts`의 `mergeSummariesByTitle()` — `projectName + normalizeTitle(topic)` 기준 그룹핑, 통계 합산(messageCount, tokens, duration), 요약 필드 통합\n- **테스트**: 135/135 통과 (11 파일, mergeSummariesByTitle 테스트 7개 추가)\n\n### v0.8.1 (2026-04-09) — Circle 크로스 디바이스 머지 + 무조건 발신 정책\n\n- **Circle 크로스 디바이스 머지**: `circle.ts`의 `autoSummarize`가 D1에서 `pullCrossDeviceReports`로 다른 기기의 이미 요약된 세션을 pull하고, `sessionId` 기준 중복 제거 후 전체 기기의 작업을 통합 요약. 기존에는 circle이 로컬 세션만 요약하고 크로스 디바이스 데이터는 `out`의 이메일 렌더링에서만 사용했음\n- **무조건 발신 정책**: `out.ts`에서 크로스 디바이스 이메일 중복 체크 제거 — 다른 기기에서 이미 발송했더라도 `out`은 항상 이메일 발신. 기존의 `checkCrossDeviceEmailSent` → skip 동작이 이메일 발신을 차단하는 문제 해결\n- **아키텍처 정렬**: 3단계 파이프라인이 명확한 역할 분리를 따름 — `air`는 기기별 수집, `circle`은 전체 기기 통합 요약, `out`은 항상 발신\n- **변경 파일**: `src/core/circle.ts` (D1 pull + merge in `autoSummarize`), `src/core/out.ts` (dedup skip 블록 제거)\n- **테스트**: 128/128 통과 (11개 테스트 파일)\n\n### v0.8.0 (2026-04-09) — 크로스 디바이스 통합 리포트 + 프로젝트별 세션 머지\n\n- **크로스 디바이스 통합 리포트**: 여러 기기에서 작업할 때 `out` 실행 시 로컬 데이터를 D1에 먼저 push(pre-sync)한 뒤 다른 기기의 세션을 pull하여 하나의 통합 이메일 리포트로 발송.\n- **프로젝트별 세션 머지**: 같은 날짜 내 동일 `프로젝트명` 세션을 자동 머지 — 통계(메시지/토큰/시간) 합산, 가장 상세한 wrapUp 채택, flow 서술을 `→` 구분자로 연결. 머지된 세션은 제목에 `(×N)` 카운트를 표시.\n- **타이틀 추출 개선**: 슬래시 명령(`/sincenety` 등)으로 시작하는 세션에서 5자 이상의 의미 있는 메시지를 우선 선택. 없으면 `[프로젝트명] session`으로 폴백하여 빈 제목을 방지.\n- **Graceful D1 fallback**: 모든 크로스 디바이스 기능이 try/catch로 감싸져 있어 D1 연결 불가 시 기존 단일 기기 동작으로 자연스럽게 전환.\n- **신규 파일**: `src/email/merge-sessions.ts`(세션 머지 유틸리티), `src/cloud/sync.ts`에 `pullCrossDeviceReports`/`checkCrossDeviceEmailSent` 추가.\n- **테스트**: 128/128 통과 (11개 테스트 파일).\n\n### v0.7.7 (2026-04-09) — claude-code 요약 품질 개선 + Workers AI CLI 샘플 리포트\n\n- **claude-code 요약 품질 개선**: `ai_provider = claude-code`일 때 `circle --json`이 `conversationTurns`를 출력 전 전처리 — 경로/파일명 제거, 단답 필터링, 30턴 제한, 200/300자 트렁케이션 적용 (Workers AI와 동일한 전처리). Claude Code의 직접 요약 품질 대폭 향상\n- **SKILL.md 2-pass 구조화**: 2단계 지시문 전면 개편 — 세션별 1-pass 분리 처리 + overview 2-pass 종합 생성. 구체적 입출력 예시 추가로 일관된 품질 보장\n- **Workers AI CLI 샘플 리포트**: `docs/sample-report-cli.html` — Workers AI(Cloudflare)가 요약한 실제 일일보고 이메일 HTML 추가. [pathcosmos.github.io/sincenety/sample-report-cli.html](https://pathcosmos.github.io/sincenety/sample-report-cli.html)에서 확인 가능\n- **테스트**: 128/128 통과 (11개 테스트 파일)\n\n### v0.7.6 (2026-04-09) — sessionId prefix 매칭 + GitHub Pages 샘플 리포트\n\n- **sessionId prefix 매칭 폴백**: `circle --save`로 AI 요약 저장 시 sessionId가 잘리거나 변형되어도 렌더러(`renderer.ts`)가 prefix(12자) 매칭으로 올바른 세션에 AI 요약을 매핑 — 이메일에서 raw 데이터로 폴백되는 문제 방지\n- **`circleSave()` 자동 교정**: 입력된 sessionId가 DB 세션과 정확히 일치하지 않으면 prefix 매칭으로 올바른 ID를 찾아 교정된 ID로 저장 — 이후 렌더링에서 항상 유효한 ID 보장\n- **GitHub Pages 샘플 리포트**: `docs/index.html` 랜딩 페이지와 `docs/sample-report.html` 실제 일일보고 이메일 샘플 추가. [pathcosmos.github.io/sincenety](https://pathcosmos.github.io/sincenety/)에서 확인 가능\n- **테스트**: 128/128 통과 (11개 테스트 파일)\n\n### v0.7.4 (2026-04-09) — AI provider 라우팅 수정 + 요약 품질 개선\n\n- **`autoSummarize()` ai_provider 미존중 버그 수정**: CLI 환경(`sincenety`, `sincenety circle`)에서 `ai_provider` 설정과 무관하게 D1 토큰만 있으면 Workers AI를 호출하던 버그 수정. 이제 `resolveAiProvider()`를 통해 `ai_provider` 설정을 존중\n- **`circleJson --summarize` provider 체크 추가**: `--summarize` 플래그도 `ai_provider = cloudflare`일 때만 Workers AI 호출\n- **Workers AI 실패 시 heuristic fallback**: 개별 세션에서 Workers AI가 실패하면 `summarizer.ts`의 heuristic 요약으로 대체 (데이터 손실 방지)\n- **`autoSummarize()` 전 AI provider 대응**: 기존에는 `cloudflare`일 때만 실행되던 것을 모든 provider에서 실행 (cloudflare → Workers AI, anthropic → Claude API, claude-code/heuristic → 휴리스틱). `daily_reports`에 항상 기본 요약이 생성됨\n- **어시스턴트 출력 캡 300 → 1500자로 확대**: 기존 300자 하드캡으로 대부분의 응답 내용이 유실되던 문제 수정. 1500자로 확대하여 요약 품질 향상\n- **파일 경로/파일명 필터링**: 절대 경로(`/Users/...`, `/Volumes/...`), 상대 경로(`./foo`, `../bar`), 확장자 포함 파일명(`.ts`, `.js`, `.json` 등)을 요약 입력에서 제거하여 기술적 노이즈 감소\n- **휴리스틱 fallback 요약 개선**: 대화 턴이 없을 때 사용자 입력 원문 대신 프로젝트명 + 메시지 수 표시; 결과 키워드 미발견 시 어시스턴트 출력의 첫 문장 추출로 대체\n- **`tool_use` 블록 추출**: Claude Code 어시스턴트 응답이 텍스트 없는 `tool_use` 블록(Edit, Bash, Read)인 경우 도구명을 `[Edit, Bash, Read]` 형태로 추출하여 휴리스틱 요약에 활용\n- **README AI 요약 엔진 섹션 갱신**: \"CLI에서는 항상 Workers AI\" → \"모든 환경에서 `ai_provider` 존중\"으로 정정\n\n### v0.7.2 (2026-04-09) — --date 옵션 + Data Flow 다이어그램 분리\n\n- **`--date yyyyMMdd` 옵션**: `out/outd/outw/outm` 명령에 특정 날짜 지정 가능\n- **Data Flow 다이어그램 분리**: Install Flow (설치 흐름) + Run Flow (실행 흐름) 2개로 분리\n- **`parseDateArg()` 헬퍼**: yyyyMMdd 파싱 + 유효성 검증 (2월30일, 13월 등 차단)\n- **테스트 12개 추가** (128/128 pass)\n\n### v0.7.0 (2026-04-09) — Scope 선택 + postinstall 셋업 위저드\n\n- **Scope 선택 (Global / Project)**: 전체 프로젝트 추적 또는 특정 프로젝트만 추적 선택 가능 (`~/.sincenety/scope.json`)\n- **postinstall 셋업 위저드**: `npm install -g` 시 대화형 3단계 설정 (scope → D1 토큰 + Cloudflare 권한 가이드 → email)\n- **D1 토큰 가이드 강화**: Cloudflare Custom Token 생성 절차 및 필요 권한(Workers AI Read, D1 Edit, Account Settings Read) 상세 안내\n- **schedule 명령 비활성화**: 향후 재구현 예정\n- **CLI 출력 영문 전환**: setup-wizard, d1-auto-setup 등 사용자 대면 메시지 영문화\n- **신규 파일**: `src/config/scope.ts`, `src/postinstall.ts`\n\n### v0.6.5 (2026-04-08) — JSON 출력 정합성 수정\n\n- **`--render-only` stdout/stderr 분리**: D1 sync 완료 메시지를 `console.error`로 변경하여 JSON stdout 오염 방지\n- **`--render-only` 단일 JSON 출력**: 다중 보고서 타입(daily+weekly 등) 렌더링 시 개별 JSON → 단일 JSON 객체/배열로 통합\n- **`--render-only` 완료 메시지 제거**: `✅ out 완료` 메시지가 JSON stdout에 섞이던 문제 수정\n- **`claude-code` AI provider 옵션 문서화**: `sincenety config --ai-provider claude-code` 옵션을 README에 반영\n\n### 향후 계획\n\n- [x] npm publish → `npx sincenety@latest` 배포\n- [x] 3단계 파이프라인 (air → circle → out)\n- [x] checkpoint 기반 백필 + 변경 감지\n- [x] 휴가 관리\n- [x] `out` 명령 — 스마트 이메일 발신 (out/outd/outw/outm, 4 p","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathcosmos%2Fsincenety","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpathcosmos%2Fsincenety","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpathcosmos%2Fsincenety/lists"}