@giwonn/claude-daily-review 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md ADDED
@@ -0,0 +1,143 @@
1
+ # claude-daily-review
2
+
3
+ Claude Code 플러그인으로, 대화 내용을 자동으로 캡처하여 Obsidian vault 또는 GitHub 저장소에 구조화된 일일 회고 마크다운 파일을 생성합니다.
4
+
5
+ AI와 함께하는 일상 개발 업무를 경력 문서로 자동 변환하세요.
6
+
7
+ ## 주요 기능
8
+
9
+ - **자동 수집**: 훅 기반 대화 기록으로 수동 작업 제로
10
+ - **구조화된 회고**: 프로젝트별 작업 요약, 배운 점, 의사결정, Q&A 정리
11
+ - **계단식 요약**: 일간 → 주간 → 월간 → 분기 → 연간
12
+ - **프로젝트 추적**: 이력서/경력기술서용 프로젝트별 누적 요약
13
+ - **Obsidian 연동**: 태그와 링크가 포함된 마크다운 직접 출력
14
+ - **GitHub 연동**: OAuth 인증으로 GitHub 저장소에 회고 저장
15
+ - **동시성 안전**: 세션 격리 쓰기 + 지연 병합으로 데이터 무결성 보장
16
+ - **강제종료 복구**: 강제 종료 시에도 원시 로그 보존
17
+
18
+ ## 설치
19
+
20
+ ```bash
21
+ claude plugin add claude-daily-review
22
+ ```
23
+
24
+ ## 설정
25
+
26
+ 처음 실행 시 자동으로 설정 안내가 표시됩니다. 또는 수동으로 실행:
27
+
28
+ ```
29
+ /daily-review-setup
30
+ ```
31
+
32
+ 설정 과정:
33
+ 1. 저장소 선택: **로컬** (Obsidian vault 등) 또는 **GitHub** (원격 저장소)
34
+ 2. 간단한 자기소개 (회사, 직무, 팀)
35
+ 3. 요약 주기 선택
36
+
37
+ ## GitHub 저장소
38
+
39
+ GitHub를 저장소 백엔드로 선택하면, **OAuth Device Flow**로 인증합니다 — 개인 액세스 토큰이 필요 없습니다.
40
+
41
+ ### 인증
42
+
43
+ 1. 플러그인이 GitHub에 디바이스 코드를 요청합니다
44
+ 2. `https://github.com/login/device`에 접속하여 표시된 코드를 입력합니다
45
+ 3. 승인하면 플러그인이 OAuth 토큰을 자동으로 수신하여 저장합니다
46
+
47
+ ### 저장소 설정
48
+
49
+ 인증 후 선택할 수 있습니다:
50
+ - **새 저장소 생성** — 계정에 비공개 저장소를 자동 생성
51
+ - **기존 저장소 사용** — owner/repo 형식으로 입력
52
+
53
+ ### 동작 방식
54
+
55
+ 파일은 **GitHub Contents API**를 통해 읽고 쓰여집니다. 각 회고 파일은 마크다운 파일로 저장소에 직접 커밋되며, 로컬 저장소와 동일한 폴더 구조를 사용합니다. 로컬 git 설치가 필요 없습니다.
56
+
57
+ ## 동작 원리
58
+
59
+ ```
60
+ 매 응답 완료 → 원시 로그 저장 (비동기, 논블로킹)
61
+ 세션 종료 → AI가 구조화된 회고 생성
62
+ 다음 세션 → 회고를 일일 파일에 병합 + 주기별 요약 생성
63
+ ```
64
+
65
+ ## 폴더 구조
66
+
67
+ ```
68
+ daily-review/
69
+ ├── daily/2026-03-28.md ← 일일 회고 (모든 프로젝트)
70
+ ├── weekly/2026-W13.md ← 주간 요약
71
+ ├── monthly/2026-03.md ← 월간 요약
72
+ ├── quarterly/2026-Q1.md ← 분기 요약
73
+ ├── yearly/2026.md ← 연간 요약
74
+ ├── projects/my-app/
75
+ │ ├── 2026-03-28.md ← 프로젝트별 일일 상세
76
+ │ └── summary.md ← 프로젝트 누적 요약
77
+ └── uncategorized/2026-03-28.md ← 프로젝트와 무관한 질문
78
+ ```
79
+
80
+ ## 설정 파일
81
+
82
+ 설정은 `$CLAUDE_PLUGIN_DATA/config.json`에 저장됩니다.
83
+
84
+ ### 로컬 저장소 예시
85
+
86
+ ```json
87
+ {
88
+ "storage": {
89
+ "type": "local",
90
+ "local": {
91
+ "basePath": "/path/to/obsidian/vault/daily-review"
92
+ }
93
+ },
94
+ "language": "ko",
95
+ "periods": {
96
+ "daily": true,
97
+ "weekly": true,
98
+ "monthly": true,
99
+ "quarterly": true,
100
+ "yearly": false
101
+ },
102
+ "profile": {
103
+ "company": "회사명",
104
+ "role": "직무",
105
+ "team": "팀명",
106
+ "context": "하는 일 한 줄 설명"
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### GitHub 저장소 예시
112
+
113
+ ```json
114
+ {
115
+ "storage": {
116
+ "type": "github",
117
+ "github": {
118
+ "owner": "github-username",
119
+ "repo": "daily-review",
120
+ "token": "<OAuth flow가 자동 저장>",
121
+ "basePath": "daily-review"
122
+ }
123
+ },
124
+ "language": "ko",
125
+ "periods": {
126
+ "daily": true,
127
+ "weekly": true,
128
+ "monthly": true,
129
+ "quarterly": true,
130
+ "yearly": false
131
+ },
132
+ "profile": {
133
+ "company": "회사명",
134
+ "role": "직무",
135
+ "team": "팀명",
136
+ "context": "하는 일 한 줄 설명"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## 라이선스
142
+
143
+ MIT
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # claude-daily-review
2
+
3
+ [한국어](README.ko.md)
4
+
5
+ Claude Code plugin that automatically captures your conversations and generates structured daily review markdown files in your Obsidian vault or GitHub repository.
6
+
7
+ Turn your daily AI-assisted development work into career documentation — automatically.
8
+
9
+ ## Features
10
+
11
+ - **Auto-capture**: Hook-based conversation logging with zero manual effort
12
+ - **Structured reviews**: Work summaries, learnings, decisions, and Q&A organized by project
13
+ - **Cascading summaries**: Daily → Weekly → Monthly → Quarterly → Yearly
14
+ - **Project tracking**: Per-project summaries for resume/portfolio building
15
+ - **Obsidian integration**: Direct markdown output with tags and links
16
+ - **GitHub integration**: Store reviews in a GitHub repo with OAuth authentication
17
+ - **Concurrency-safe**: Session-isolated writes with deferred merge
18
+ - **Crash recovery**: Raw logs preserved even on force-quit
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ claude plugin add claude-daily-review
24
+ ```
25
+
26
+ ## Setup
27
+
28
+ On first run, you'll be prompted to configure the plugin. Or run manually:
29
+
30
+ ```
31
+ /daily-review-setup
32
+ ```
33
+
34
+ This will ask for:
35
+ 1. Your storage choice: **local** (Obsidian vault or any directory) or **GitHub** (remote repository)
36
+ 2. A brief professional profile (company, role, team)
37
+ 3. Which summary periods to enable
38
+
39
+ ## GitHub Storage
40
+
41
+ When you choose GitHub as your storage backend, the plugin authenticates using the **OAuth Device Flow** — no personal access tokens required.
42
+
43
+ ### Authentication
44
+
45
+ 1. The plugin requests a device code from GitHub
46
+ 2. You visit `https://github.com/login/device` and enter the code
47
+ 3. After you authorize, the plugin receives and stores an OAuth token automatically
48
+
49
+ ### Repository Setup
50
+
51
+ After authenticating, you can either:
52
+ - **Create a new repository** — the plugin creates a private repo on your account
53
+ - **Use an existing repository** — enter the owner and repo name to use a repo you already have
54
+
55
+ ### How It Works
56
+
57
+ Files are read and written via the **GitHub Contents API**. Each review file is committed directly to the repository as a markdown file, using the same folder structure as local storage. No local git installation is required.
58
+
59
+ ## How It Works
60
+
61
+ ```
62
+ Every response → Raw log saved (async, non-blocking)
63
+ Session end → AI generates structured review
64
+ Next session → Reviews merged into daily file + periodic summaries generated
65
+ ```
66
+
67
+ ## Vault Structure
68
+
69
+ ```
70
+ vault/daily-review/
71
+ ├── daily/2026-03-28.md ← Daily review (all projects)
72
+ ├── weekly/2026-W13.md ← Weekly summary
73
+ ├── monthly/2026-03.md ← Monthly summary
74
+ ├── quarterly/2026-Q1.md ← Quarterly summary
75
+ ├── yearly/2026.md ← Yearly summary
76
+ ├── projects/my-app/
77
+ │ ├── 2026-03-28.md ← Project daily detail
78
+ │ └── summary.md ← Cumulative project summary
79
+ └── uncategorized/2026-03-28.md ← Non-project questions
80
+ ```
81
+
82
+ ## Configuration
83
+
84
+ Config is stored at `$CLAUDE_PLUGIN_DATA/config.json`.
85
+
86
+ ### Local storage example
87
+
88
+ ```json
89
+ {
90
+ "storage": {
91
+ "type": "local",
92
+ "vaultPath": "/path/to/obsidian/vault"
93
+ },
94
+ "reviewFolder": "daily-review",
95
+ "language": "ko",
96
+ "periods": {
97
+ "daily": true,
98
+ "weekly": true,
99
+ "monthly": true,
100
+ "quarterly": true,
101
+ "yearly": false
102
+ },
103
+ "profile": {
104
+ "company": "Your Company",
105
+ "role": "Your Role",
106
+ "team": "Your Team",
107
+ "context": "What you do in one line"
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### GitHub storage example
113
+
114
+ ```json
115
+ {
116
+ "storage": {
117
+ "type": "github",
118
+ "owner": "your-github-username",
119
+ "repo": "daily-review",
120
+ "branch": "main",
121
+ "token": "<stored by OAuth flow>"
122
+ },
123
+ "reviewFolder": "daily-review",
124
+ "language": "ko",
125
+ "periods": {
126
+ "daily": true,
127
+ "weekly": true,
128
+ "monthly": true,
129
+ "quarterly": true,
130
+ "yearly": false
131
+ },
132
+ "profile": {
133
+ "company": "Your Company",
134
+ "role": "Your Role",
135
+ "team": "Your Team",
136
+ "context": "What you do in one line"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,274 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/core/github-storage.ts
12
+ var github_storage_exports = {};
13
+ __export(github_storage_exports, {
14
+ GitHubStorageAdapter: () => GitHubStorageAdapter
15
+ });
16
+ var GitHubStorageAdapter;
17
+ var init_github_storage = __esm({
18
+ "src/core/github-storage.ts"() {
19
+ "use strict";
20
+ GitHubStorageAdapter = class {
21
+ constructor(owner, repo, token, basePath) {
22
+ this.owner = owner;
23
+ this.repo = repo;
24
+ this.token = token;
25
+ this.basePath = basePath;
26
+ this.baseUrl = `https://api.github.com/repos/${owner}/${repo}/contents`;
27
+ this.headers = {
28
+ Authorization: `Bearer ${token}`,
29
+ Accept: "application/vnd.github.v3+json",
30
+ "Content-Type": "application/json"
31
+ };
32
+ }
33
+ baseUrl;
34
+ headers;
35
+ getUrl(path) {
36
+ return `${this.baseUrl}/${this.basePath}/${path}`;
37
+ }
38
+ async getSha(path) {
39
+ const res = await fetch(this.getUrl(path), { method: "GET", headers: this.headers });
40
+ if (res.status === 404) return null;
41
+ const data = await res.json();
42
+ return data.sha || null;
43
+ }
44
+ async read(path) {
45
+ const res = await fetch(this.getUrl(path), { method: "GET", headers: this.headers });
46
+ if (res.status === 404) return null;
47
+ const data = await res.json();
48
+ const content = data.content;
49
+ return Buffer.from(content, "base64").toString("utf-8");
50
+ }
51
+ async write(path, content) {
52
+ const sha = await this.getSha(path);
53
+ const body = {
54
+ message: `update ${path}`,
55
+ content: Buffer.from(content).toString("base64")
56
+ };
57
+ if (sha) body.sha = sha;
58
+ const res = await fetch(this.getUrl(path), {
59
+ method: "PUT",
60
+ headers: this.headers,
61
+ body: JSON.stringify(body)
62
+ });
63
+ if (!res.ok && res.status === 409) {
64
+ const freshSha = await this.getSha(path);
65
+ if (freshSha) body.sha = freshSha;
66
+ await fetch(this.getUrl(path), {
67
+ method: "PUT",
68
+ headers: this.headers,
69
+ body: JSON.stringify(body)
70
+ });
71
+ }
72
+ }
73
+ async append(path, content) {
74
+ const existing = await this.read(path);
75
+ const newContent = existing ? existing + content : content;
76
+ await this.write(path, newContent);
77
+ }
78
+ async exists(path) {
79
+ const res = await fetch(this.getUrl(path), { method: "GET", headers: this.headers });
80
+ return res.status !== 404;
81
+ }
82
+ async list(dir) {
83
+ const res = await fetch(this.getUrl(dir), { method: "GET", headers: this.headers });
84
+ if (res.status === 404) return [];
85
+ const data = await res.json();
86
+ if (!Array.isArray(data)) return [];
87
+ return data.map((entry) => entry.name);
88
+ }
89
+ async mkdir(_dir) {
90
+ }
91
+ async isDirectory(path) {
92
+ const res = await fetch(this.getUrl(path), { method: "GET", headers: this.headers });
93
+ if (res.status === 404) return false;
94
+ const data = await res.json();
95
+ return Array.isArray(data);
96
+ }
97
+ };
98
+ }
99
+ });
100
+
101
+ // src/hooks/on-stop.ts
102
+ import { fileURLToPath } from "url";
103
+ import { resolve } from "path";
104
+
105
+ // src/core/config.ts
106
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
107
+ import { dirname as dirname2, join as join2 } from "path";
108
+
109
+ // src/core/local-storage.ts
110
+ import {
111
+ readFileSync,
112
+ writeFileSync,
113
+ appendFileSync,
114
+ existsSync,
115
+ mkdirSync,
116
+ readdirSync,
117
+ statSync
118
+ } from "fs";
119
+ import { dirname, join } from "path";
120
+ var LocalStorageAdapter = class {
121
+ constructor(basePath) {
122
+ this.basePath = basePath;
123
+ }
124
+ resolve(path) {
125
+ return join(this.basePath, path);
126
+ }
127
+ async read(path) {
128
+ const full = this.resolve(path);
129
+ if (!existsSync(full)) return null;
130
+ return readFileSync(full, "utf-8");
131
+ }
132
+ async write(path, content) {
133
+ const full = this.resolve(path);
134
+ mkdirSync(dirname(full), { recursive: true });
135
+ writeFileSync(full, content, "utf-8");
136
+ }
137
+ async append(path, content) {
138
+ const full = this.resolve(path);
139
+ mkdirSync(dirname(full), { recursive: true });
140
+ appendFileSync(full, content, "utf-8");
141
+ }
142
+ async exists(path) {
143
+ return existsSync(this.resolve(path));
144
+ }
145
+ async list(dir) {
146
+ const full = this.resolve(dir);
147
+ if (!existsSync(full)) return [];
148
+ return readdirSync(full);
149
+ }
150
+ async mkdir(dir) {
151
+ mkdirSync(this.resolve(dir), { recursive: true });
152
+ }
153
+ async isDirectory(path) {
154
+ try {
155
+ return statSync(this.resolve(path)).isDirectory();
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ };
161
+
162
+ // src/core/config.ts
163
+ function getConfigPath() {
164
+ const dataDir = process.env.CLAUDE_PLUGIN_DATA;
165
+ if (!dataDir) {
166
+ throw new Error("CLAUDE_PLUGIN_DATA environment variable is not set");
167
+ }
168
+ return join2(dataDir, "config.json");
169
+ }
170
+ function isOldConfig(raw) {
171
+ if (!raw || typeof raw !== "object") return false;
172
+ return "vaultPath" in raw && "reviewFolder" in raw;
173
+ }
174
+ function migrateOldConfig(old) {
175
+ return {
176
+ storage: {
177
+ type: "local",
178
+ local: {
179
+ basePath: join2(old.vaultPath, old.reviewFolder)
180
+ }
181
+ },
182
+ language: old.language,
183
+ periods: old.periods,
184
+ profile: old.profile
185
+ };
186
+ }
187
+ function loadConfig() {
188
+ const configPath = getConfigPath();
189
+ if (!existsSync2(configPath)) return null;
190
+ const raw = JSON.parse(readFileSync2(configPath, "utf-8"));
191
+ if (isOldConfig(raw)) {
192
+ const migrated = migrateOldConfig(raw);
193
+ saveConfig(migrated);
194
+ return migrated;
195
+ }
196
+ return raw;
197
+ }
198
+ function saveConfig(config) {
199
+ const configPath = getConfigPath();
200
+ mkdirSync2(dirname2(configPath), { recursive: true });
201
+ writeFileSync2(configPath, JSON.stringify(config, null, 2), "utf-8");
202
+ }
203
+ async function createStorageAdapter(config) {
204
+ if (config.storage.type === "local") {
205
+ return new LocalStorageAdapter(config.storage.local.basePath);
206
+ }
207
+ if (config.storage.type === "github") {
208
+ const { GitHubStorageAdapter: GitHubStorageAdapter2 } = await Promise.resolve().then(() => (init_github_storage(), github_storage_exports));
209
+ const g = config.storage.github;
210
+ return new GitHubStorageAdapter2(g.owner, g.repo, g.token, g.basePath);
211
+ }
212
+ throw new Error(`Unknown storage type: ${config.storage.type}`);
213
+ }
214
+
215
+ // src/core/raw-logger.ts
216
+ function parseHookInput(raw) {
217
+ const parsed = JSON.parse(raw);
218
+ if (!parsed || typeof parsed !== "object") {
219
+ throw new Error("Invalid hook input: expected object");
220
+ }
221
+ if (typeof parsed.session_id !== "string" || !parsed.session_id) {
222
+ throw new Error("Invalid hook input: missing session_id");
223
+ }
224
+ return parsed;
225
+ }
226
+ async function appendRawLog(storage, sessionDir, date, entry) {
227
+ await storage.mkdir(sessionDir);
228
+ const logPath = `${sessionDir}/${date}.jsonl`;
229
+ const record = {
230
+ ...entry,
231
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
232
+ };
233
+ await storage.append(logPath, JSON.stringify(record) + "\n");
234
+ }
235
+
236
+ // src/core/vault.ts
237
+ function getRawDir(sessionId) {
238
+ return `.raw/${sessionId}`;
239
+ }
240
+
241
+ // src/core/periods.ts
242
+ function formatDate(date) {
243
+ const y = date.getFullYear();
244
+ const m = String(date.getMonth() + 1).padStart(2, "0");
245
+ const d = String(date.getDate()).padStart(2, "0");
246
+ return `${y}-${m}-${d}`;
247
+ }
248
+
249
+ // src/hooks/on-stop.ts
250
+ async function handleStopHook(stdinData) {
251
+ try {
252
+ const config = loadConfig();
253
+ if (!config) return;
254
+ const storage = await createStorageAdapter(config);
255
+ const input = parseHookInput(stdinData);
256
+ const sessionDir = getRawDir(input.session_id);
257
+ const date = formatDate(/* @__PURE__ */ new Date());
258
+ await appendRawLog(storage, sessionDir, date, input);
259
+ } catch {
260
+ }
261
+ }
262
+ var isMainModule = process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1]);
263
+ if (isMainModule) {
264
+ let data = "";
265
+ process.stdin.setEncoding("utf-8");
266
+ process.stdin.on("data", (chunk) => data += chunk);
267
+ process.stdin.on("end", () => {
268
+ handleStopHook(data);
269
+ });
270
+ }
271
+ export {
272
+ handleStopHook
273
+ };
274
+ //# sourceMappingURL=on-stop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/github-storage.ts","../src/hooks/on-stop.ts","../src/core/config.ts","../src/core/local-storage.ts","../src/core/raw-logger.ts","../src/core/vault.ts","../src/core/periods.ts"],"sourcesContent":["import type { StorageAdapter } from \"./storage.js\";\n\nexport class GitHubStorageAdapter implements StorageAdapter {\n private baseUrl: string;\n private headers: Record<string, string>;\n\n constructor(\n private owner: string,\n private repo: string,\n private token: string,\n private basePath: string,\n ) {\n this.baseUrl = `https://api.github.com/repos/${owner}/${repo}/contents`;\n this.headers = {\n Authorization: `Bearer ${token}`,\n Accept: \"application/vnd.github.v3+json\",\n \"Content-Type\": \"application/json\",\n };\n }\n\n private getUrl(path: string): string {\n return `${this.baseUrl}/${this.basePath}/${path}`;\n }\n\n private async getSha(path: string): Promise<string | null> {\n const res = await fetch(this.getUrl(path), { method: \"GET\", headers: this.headers });\n if (res.status === 404) return null;\n const data = await res.json() as Record<string, unknown>;\n return (data.sha as string) || null;\n }\n\n async read(path: string): Promise<string | null> {\n const res = await fetch(this.getUrl(path), { method: \"GET\", headers: this.headers });\n if (res.status === 404) return null;\n const data = await res.json() as Record<string, unknown>;\n const content = data.content as string;\n return Buffer.from(content, \"base64\").toString(\"utf-8\");\n }\n\n async write(path: string, content: string): Promise<void> {\n const sha = await this.getSha(path);\n const body: Record<string, unknown> = {\n message: `update ${path}`,\n content: Buffer.from(content).toString(\"base64\"),\n };\n if (sha) body.sha = sha;\n\n const res = await fetch(this.getUrl(path), {\n method: \"PUT\",\n headers: this.headers,\n body: JSON.stringify(body),\n });\n\n if (!res.ok && res.status === 409) {\n const freshSha = await this.getSha(path);\n if (freshSha) body.sha = freshSha;\n await fetch(this.getUrl(path), {\n method: \"PUT\",\n headers: this.headers,\n body: JSON.stringify(body),\n });\n }\n }\n\n async append(path: string, content: string): Promise<void> {\n const existing = await this.read(path);\n const newContent = existing ? existing + content : content;\n await this.write(path, newContent);\n }\n\n async exists(path: string): Promise<boolean> {\n const res = await fetch(this.getUrl(path), { method: \"GET\", headers: this.headers });\n return res.status !== 404;\n }\n\n async list(dir: string): Promise<string[]> {\n const res = await fetch(this.getUrl(dir), { method: \"GET\", headers: this.headers });\n if (res.status === 404) return [];\n const data = await res.json() as Array<{ name: string }>;\n if (!Array.isArray(data)) return [];\n return data.map((entry) => entry.name);\n }\n\n async mkdir(_dir: string): Promise<void> {\n // GitHub creates directories implicitly when files are created\n }\n\n async isDirectory(path: string): Promise<boolean> {\n const res = await fetch(this.getUrl(path), { method: \"GET\", headers: this.headers });\n if (res.status === 404) return false;\n const data = await res.json();\n return Array.isArray(data);\n }\n}\n","// src/hooks/on-stop.ts\nimport { fileURLToPath } from \"url\";\nimport { resolve } from \"path\";\nimport { loadConfig, createStorageAdapter } from \"../core/config.js\";\nimport { parseHookInput, appendRawLog } from \"../core/raw-logger.js\";\nimport { getRawDir } from \"../core/vault.js\";\nimport { formatDate } from \"../core/periods.js\";\n\nexport async function handleStopHook(stdinData: string): Promise<void> {\n try {\n const config = loadConfig();\n if (!config) return;\n\n const storage = await createStorageAdapter(config);\n const input = parseHookInput(stdinData);\n const sessionDir = getRawDir(input.session_id);\n const date = formatDate(new Date());\n\n await appendRawLog(storage, sessionDir, date, input);\n } catch {\n // async hook — fail silently\n }\n}\n\n// Main execution\nconst isMainModule = process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1]);\n\nif (isMainModule) {\n let data = \"\";\n process.stdin.setEncoding(\"utf-8\");\n process.stdin.on(\"data\", (chunk) => (data += chunk));\n process.stdin.on(\"end\", () => {\n handleStopHook(data);\n });\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport type { StorageAdapter } from \"./storage.js\";\nimport { LocalStorageAdapter } from \"./local-storage.js\";\n\nexport interface Profile {\n company: string;\n role: string;\n team: string;\n context: string;\n}\n\nexport interface Periods {\n daily: true;\n weekly: boolean;\n monthly: boolean;\n quarterly: boolean;\n yearly: boolean;\n}\n\nexport interface LocalStorageConfig {\n basePath: string;\n}\n\nexport interface GitHubStorageConfig {\n owner: string;\n repo: string;\n token: string;\n basePath: string;\n}\n\nexport interface StorageConfig {\n type: \"local\" | \"github\";\n local?: LocalStorageConfig;\n github?: GitHubStorageConfig;\n}\n\nexport interface Config {\n storage: StorageConfig;\n language: string;\n periods: Periods;\n profile: Profile;\n}\n\ninterface OldConfig {\n vaultPath: string;\n reviewFolder: string;\n language: string;\n periods: Periods;\n profile: Profile;\n}\n\nconst DEFAULT_PERIODS: Periods = {\n daily: true,\n weekly: true,\n monthly: true,\n quarterly: true,\n yearly: false,\n};\n\nconst DEFAULT_PROFILE: Profile = {\n company: \"\",\n role: \"\",\n team: \"\",\n context: \"\",\n};\n\nexport function getConfigPath(): string {\n const dataDir = process.env.CLAUDE_PLUGIN_DATA;\n if (!dataDir) {\n throw new Error(\"CLAUDE_PLUGIN_DATA environment variable is not set\");\n }\n return join(dataDir, \"config.json\");\n}\n\nfunction isOldConfig(raw: unknown): raw is OldConfig {\n if (!raw || typeof raw !== \"object\") return false;\n return \"vaultPath\" in raw && \"reviewFolder\" in raw;\n}\n\nfunction migrateOldConfig(old: OldConfig): Config {\n return {\n storage: {\n type: \"local\",\n local: {\n basePath: join(old.vaultPath, old.reviewFolder),\n },\n },\n language: old.language,\n periods: old.periods,\n profile: old.profile,\n };\n}\n\nexport function loadConfig(): Config | null {\n const configPath = getConfigPath();\n if (!existsSync(configPath)) return null;\n const raw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n if (isOldConfig(raw)) {\n const migrated = migrateOldConfig(raw);\n saveConfig(migrated);\n return migrated;\n }\n return raw as Config;\n}\n\nexport function saveConfig(config: Config): void {\n const configPath = getConfigPath();\n mkdirSync(dirname(configPath), { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2), \"utf-8\");\n}\n\nexport function validateConfig(config: unknown): config is Config {\n if (!config || typeof config !== \"object\") return false;\n const c = config as Record<string, unknown>;\n if (!c.storage || typeof c.storage !== \"object\") return false;\n const s = c.storage as Record<string, unknown>;\n if (s.type !== \"local\" && s.type !== \"github\") return false;\n if (s.type === \"local\") {\n if (!s.local || typeof s.local !== \"object\") return false;\n const l = s.local as Record<string, unknown>;\n if (typeof l.basePath !== \"string\" || l.basePath === \"\") return false;\n }\n if (s.type === \"github\") {\n if (!s.github || typeof s.github !== \"object\") return false;\n const g = s.github as Record<string, unknown>;\n if (typeof g.owner !== \"string\" || !g.owner) return false;\n if (typeof g.repo !== \"string\" || !g.repo) return false;\n if (typeof g.token !== \"string\" || !g.token) return false;\n }\n return true;\n}\n\nexport function createDefaultLocalConfig(basePath: string): Config {\n return {\n storage: { type: \"local\", local: { basePath } },\n language: \"ko\",\n periods: { ...DEFAULT_PERIODS },\n profile: { ...DEFAULT_PROFILE },\n };\n}\n\nexport function createDefaultGitHubConfig(owner: string, repo: string, token: string): Config {\n return {\n storage: { type: \"github\", github: { owner, repo, token, basePath: \"daily-review\" } },\n language: \"ko\",\n periods: { ...DEFAULT_PERIODS },\n profile: { ...DEFAULT_PROFILE },\n };\n}\n\nexport async function createStorageAdapter(config: Config): Promise<StorageAdapter> {\n if (config.storage.type === \"local\") {\n return new LocalStorageAdapter(config.storage.local!.basePath);\n }\n if (config.storage.type === \"github\") {\n const { GitHubStorageAdapter } = await import(\"./github-storage.js\");\n const g = config.storage.github!;\n return new GitHubStorageAdapter(g.owner, g.repo, g.token, g.basePath);\n }\n throw new Error(`Unknown storage type: ${(config.storage as any).type}`);\n}\n","import {\n readFileSync,\n writeFileSync,\n appendFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n statSync,\n} from \"fs\";\nimport { dirname, join } from \"path\";\nimport type { StorageAdapter } from \"./storage.js\";\n\nexport class LocalStorageAdapter implements StorageAdapter {\n constructor(private basePath: string) {}\n\n private resolve(path: string): string {\n return join(this.basePath, path);\n }\n\n async read(path: string): Promise<string | null> {\n const full = this.resolve(path);\n if (!existsSync(full)) return null;\n return readFileSync(full, \"utf-8\");\n }\n\n async write(path: string, content: string): Promise<void> {\n const full = this.resolve(path);\n mkdirSync(dirname(full), { recursive: true });\n writeFileSync(full, content, \"utf-8\");\n }\n\n async append(path: string, content: string): Promise<void> {\n const full = this.resolve(path);\n mkdirSync(dirname(full), { recursive: true });\n appendFileSync(full, content, \"utf-8\");\n }\n\n async exists(path: string): Promise<boolean> {\n return existsSync(this.resolve(path));\n }\n\n async list(dir: string): Promise<string[]> {\n const full = this.resolve(dir);\n if (!existsSync(full)) return [];\n return readdirSync(full);\n }\n\n async mkdir(dir: string): Promise<void> {\n mkdirSync(this.resolve(dir), { recursive: true });\n }\n\n async isDirectory(path: string): Promise<boolean> {\n try {\n return statSync(this.resolve(path)).isDirectory();\n } catch {\n return false;\n }\n }\n}\n","// src/core/raw-logger.ts\nimport type { StorageAdapter } from \"./storage.js\";\n\nexport interface HookInput {\n session_id: string;\n transcript_path: string;\n cwd: string;\n hook_event_name: string;\n [key: string]: unknown;\n}\n\nexport function parseHookInput(raw: string): HookInput {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\"Invalid hook input: expected object\");\n }\n if (typeof parsed.session_id !== \"string\" || !parsed.session_id) {\n throw new Error(\"Invalid hook input: missing session_id\");\n }\n return parsed as HookInput;\n}\n\nexport async function appendRawLog(storage: StorageAdapter, sessionDir: string, date: string, entry: HookInput): Promise<void> {\n await storage.mkdir(sessionDir);\n const logPath = `${sessionDir}/${date}.jsonl`;\n const record = {\n ...entry,\n timestamp: new Date().toISOString(),\n };\n await storage.append(logPath, JSON.stringify(record) + \"\\n\");\n}\n","// src/core/vault.ts\nimport type { StorageAdapter } from \"./storage.js\";\nimport type { Periods } from \"./config.js\";\n\nexport function getRawDir(sessionId: string): string {\n return `.raw/${sessionId}`;\n}\n\nexport function getReviewsDir(): string {\n return \".reviews\";\n}\n\nexport function getDailyPath(date: string): string {\n return `daily/${date}.md`;\n}\n\nexport function getWeeklyPath(week: string): string {\n return `weekly/${week}.md`;\n}\n\nexport function getMonthlyPath(month: string): string {\n return `monthly/${month}.md`;\n}\n\nexport function getQuarterlyPath(quarter: string): string {\n return `quarterly/${quarter}.md`;\n}\n\nexport function getYearlyPath(year: string): string {\n return `yearly/${year}.md`;\n}\n\nexport function getProjectDailyPath(projectName: string, date: string): string {\n return `projects/${projectName}/${date}.md`;\n}\n\nexport function getProjectSummaryPath(projectName: string): string {\n return `projects/${projectName}/summary.md`;\n}\n\nexport function getUncategorizedPath(date: string): string {\n return `uncategorized/${date}.md`;\n}\n\nexport async function ensureVaultDirectories(storage: StorageAdapter, periods: Periods): Promise<void> {\n const dirs = [\"daily\", \"projects\", \"uncategorized\", \".raw\", \".reviews\"];\n if (periods.weekly) dirs.push(\"weekly\");\n if (periods.monthly) dirs.push(\"monthly\");\n if (periods.quarterly) dirs.push(\"quarterly\");\n if (periods.yearly) dirs.push(\"yearly\");\n\n for (const dir of dirs) {\n await storage.mkdir(dir);\n }\n}\n","export function getISOWeek(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n\nexport function getISOWeekYear(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n return d.getUTCFullYear();\n}\n\nexport function getQuarter(date: Date): number {\n return Math.ceil((date.getMonth() + 1) / 3);\n}\n\nexport function formatDate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nexport function formatWeek(date: Date): string {\n return `${getISOWeekYear(date)}-W${String(getISOWeek(date)).padStart(2, \"0\")}`;\n}\n\nexport function formatMonth(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}`;\n}\n\nexport function formatQuarter(date: Date): string {\n return `${date.getFullYear()}-Q${getQuarter(date)}`;\n}\n\nexport function formatYear(date: Date): string {\n return `${date.getFullYear()}`;\n}\n\nexport interface PeriodCheck {\n needsWeekly: boolean;\n needsMonthly: boolean;\n needsQuarterly: boolean;\n needsYearly: boolean;\n previousWeek: string;\n previousMonth: string;\n previousQuarter: string;\n previousYear: string;\n}\n\nexport function checkPeriodsNeeded(today: Date, lastRun: Date | null): PeriodCheck {\n if (!lastRun) {\n return {\n needsWeekly: false,\n needsMonthly: false,\n needsQuarterly: false,\n needsYearly: false,\n previousWeek: \"\",\n previousMonth: \"\",\n previousQuarter: \"\",\n previousYear: \"\",\n };\n }\n\n const todayWeek = formatWeek(today);\n const lastWeek = formatWeek(lastRun);\n const todayMonth = formatMonth(today);\n const lastMonth = formatMonth(lastRun);\n const todayQuarter = formatQuarter(today);\n const lastQuarter = formatQuarter(lastRun);\n const todayYear = formatYear(today);\n const lastYear = formatYear(lastRun);\n\n return {\n needsWeekly: todayWeek !== lastWeek,\n needsMonthly: todayMonth !== lastMonth,\n needsQuarterly: todayQuarter !== lastQuarter,\n needsYearly: todayYear !== lastYear,\n previousWeek: lastWeek,\n previousMonth: lastMonth,\n previousQuarter: lastQuarter,\n previousYear: lastYear,\n };\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","mkdirSync","dirname","join","join","existsSync","readFileSync","mkdirSync","dirname","writeFileSync","GitHubStorageAdapter"]}