@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 +143 -0
- package/README.md +143 -0
- package/dist/on-stop.js +274 -0
- package/dist/on-stop.js.map +1 -0
- package/dist/storage-cli.js +267 -0
- package/dist/storage-cli.js.map +1 -0
- package/hooks/hooks.json +38 -0
- package/package.json +31 -0
- package/prompts/session-end.md +233 -0
- package/prompts/session-start.md +358 -0
- package/skills/daily-review-setup.md +178 -0
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
|
package/dist/on-stop.js
ADDED
|
@@ -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"]}
|