@giwonn/claude-daily-review 0.2.3 → 0.3.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.
@@ -0,0 +1,333 @@
1
+ # No-Build 플러그인 구조 전환 설계 문서
2
+
3
+ ## 1. 개요
4
+
5
+ TypeScript + tsup 빌드 구조를 제거하고, 순수 `.mjs` + JSDoc 기반으로 전환한다. Claude Code 플러그인 생태계 표준 패턴(빌드 없음, 직접 실행)을 따른다. marketplace.json의 SHA 업데이트를 CI에서 자동화한다.
6
+
7
+ ## 2. 목표
8
+
9
+ - 빌드 단계 제거 (tsup, typescript 의존성 제거)
10
+ - `.mjs` + JSDoc으로 타입 안전성 유지
11
+ - dist/ 제거, 소스 파일을 직접 실행
12
+ - superpowers 플러그인 패턴 준수 (run-hook.cmd polyglot wrapper)
13
+ - marketplace.json SHA 자동 업데이트 CI 워크플로우
14
+
15
+ ## 3. 파일 구조
16
+
17
+ ### 제거 대상
18
+
19
+ ```
20
+ src/ ← 전체 제거
21
+ dist/ ← 전체 제거
22
+ tests/ ← 전체 제거
23
+ tsconfig.json ← 제거
24
+ tsup.config.ts ← 제거
25
+ vitest.config.ts ← 제거
26
+ ```
27
+
28
+ ### 새 구조
29
+
30
+ ```
31
+ claude-daily-review/
32
+ ├── .claude-plugin/
33
+ │ └── marketplace.json
34
+ ├── .github/
35
+ │ └── workflows/
36
+ │ ├── publish.yml ← npm publish (기존)
37
+ │ └── update-marketplace.yml ← SHA 자동 업데이트 (신규)
38
+ ├── hooks/
39
+ │ ├── hooks.json
40
+ │ ├── run-hook.cmd ← Windows+Unix polyglot wrapper
41
+ │ ├── session-start-check ← bash 스크립트 (config 체크 + additionalContext)
42
+ │ └── on-stop.mjs ← Node.js (raw log append)
43
+ ├── lib/
44
+ │ ├── types.d.ts ← TypeScript 타입 정의 (JSDoc용)
45
+ │ ├── config.mjs ← 설정 관리
46
+ │ ├── storage.mjs ← StorageAdapter 인터페이스 구현 (local)
47
+ │ ├── github-storage.mjs ← GitHubStorageAdapter
48
+ │ ├── github-auth.mjs ← OAuth Device Flow
49
+ │ └── periods.mjs ← 날짜/주기 유틸리티
50
+ ├── prompts/
51
+ │ ├── session-end.md
52
+ │ └── session-start.md
53
+ ├── skills/
54
+ │ └── daily-review-setup.md
55
+ ├── package.json ← 최소화 (이름, 버전, type: module)
56
+ ├── README.md
57
+ └── README.ko.md
58
+ ```
59
+
60
+ ## 4. 모듈별 변환
61
+
62
+ ### 4.1 lib/types.d.ts
63
+
64
+ JSDoc에서 참조할 타입 정의. 런타임에는 사용되지 않음.
65
+
66
+ ```typescript
67
+ export interface Profile {
68
+ company: string;
69
+ role: string;
70
+ team: string;
71
+ context: string;
72
+ }
73
+
74
+ export interface Periods {
75
+ daily: true;
76
+ weekly: boolean;
77
+ monthly: boolean;
78
+ quarterly: boolean;
79
+ yearly: boolean;
80
+ }
81
+
82
+ export interface LocalStorageConfig {
83
+ basePath: string;
84
+ }
85
+
86
+ export interface GitHubStorageConfig {
87
+ owner: string;
88
+ repo: string;
89
+ token: string;
90
+ basePath: string;
91
+ }
92
+
93
+ export interface StorageConfig {
94
+ type: "local" | "github";
95
+ local?: LocalStorageConfig;
96
+ github?: GitHubStorageConfig;
97
+ }
98
+
99
+ export interface Config {
100
+ storage: StorageConfig;
101
+ language: string;
102
+ periods: Periods;
103
+ profile: Profile;
104
+ }
105
+
106
+ export interface StorageAdapter {
107
+ read(path: string): Promise<string | null>;
108
+ write(path: string, content: string): Promise<void>;
109
+ append(path: string, content: string): Promise<void>;
110
+ exists(path: string): Promise<boolean>;
111
+ list(dir: string): Promise<string[]>;
112
+ mkdir(dir: string): Promise<void>;
113
+ isDirectory(path: string): Promise<boolean>;
114
+ }
115
+
116
+ export interface HookInput {
117
+ session_id: string;
118
+ transcript_path: string;
119
+ cwd: string;
120
+ hook_event_name: string;
121
+ [key: string]: unknown;
122
+ }
123
+ ```
124
+
125
+ ### 4.2 lib/config.mjs
126
+
127
+ 기존 `src/core/config.ts`를 `.mjs` + JSDoc으로 변환.
128
+
129
+ ```javascript
130
+ // @ts-check
131
+ /** @typedef {import('./types.d.ts').Config} Config */
132
+
133
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
134
+ import { dirname, join } from 'path';
135
+
136
+ /** @returns {string} */
137
+ export function getConfigPath() { ... }
138
+
139
+ /** @returns {Config | null} */
140
+ export function loadConfig() { ... }
141
+ // ... 동일한 로직, JSDoc 타입 어노테이션만 추가
142
+ ```
143
+
144
+ ### 4.3 lib/storage.mjs
145
+
146
+ `LocalStorageAdapter`를 클래스로 구현.
147
+
148
+ ```javascript
149
+ // @ts-check
150
+ /** @typedef {import('./types.d.ts').StorageAdapter} StorageAdapter */
151
+
152
+ /** @implements {StorageAdapter} */
153
+ export class LocalStorageAdapter { ... }
154
+ ```
155
+
156
+ ### 4.4 lib/github-storage.mjs
157
+
158
+ `GitHubStorageAdapter`를 클래스로 구현. `fetch` 사용 (Node.js 내장).
159
+
160
+ ### 4.5 lib/github-auth.mjs
161
+
162
+ OAuth Device Flow. `fetch` 사용.
163
+
164
+ ### 4.6 lib/periods.mjs
165
+
166
+ 날짜/주기 유틸리티. 순수 함수.
167
+
168
+ ### 4.7 hooks/on-stop.mjs
169
+
170
+ ```javascript
171
+ #!/usr/bin/env node
172
+ // stdin → raw log append
173
+ import { loadConfig, createStorageAdapter } from '../lib/config.mjs';
174
+ // ...
175
+ ```
176
+
177
+ ### 4.8 hooks/session-start-check
178
+
179
+ ```bash
180
+ #!/usr/bin/env bash
181
+ # superpowers 패턴: config 체크 → additionalContext 주입
182
+ set -euo pipefail
183
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
184
+ PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
185
+
186
+ # Node.js로 config 체크
187
+ result=$(node -e "
188
+ import { loadConfig } from '${PLUGIN_ROOT}/lib/config.mjs';
189
+ const config = loadConfig();
190
+ if (!config) process.stdout.write('NEEDS_SETUP');
191
+ " 2>/dev/null || echo "NEEDS_SETUP")
192
+
193
+ if [ "$result" = "NEEDS_SETUP" ]; then
194
+ msg="<important-reminder>IN YOUR FIRST REPLY YOU MUST TELL THE USER: daily-review 플러그인이 아직 설정되지 않았습니다. /daily-review-setup 을 실행해주세요.</important-reminder>"
195
+ escaped=$(echo "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
196
+ printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$escaped"
197
+ fi
198
+
199
+ exit 0
200
+ ```
201
+
202
+ ### 4.9 hooks/run-hook.cmd
203
+
204
+ superpowers의 polyglot wrapper를 그대로 복사하여 Windows + Unix 모두 지원.
205
+
206
+ ## 5. hooks/hooks.json
207
+
208
+ ```json
209
+ {
210
+ "hooks": {
211
+ "Stop": [
212
+ {
213
+ "hooks": [
214
+ {
215
+ "type": "command",
216
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/on-stop.mjs\"",
217
+ "async": true,
218
+ "timeout": 10
219
+ }
220
+ ]
221
+ }
222
+ ],
223
+ "SessionEnd": [
224
+ {
225
+ "hooks": [
226
+ {
227
+ "type": "agent",
228
+ "prompt": "Follow the instructions in the file at ${CLAUDE_PLUGIN_ROOT}/prompts/session-end.md exactly. The CLAUDE_PLUGIN_DATA directory is: ${CLAUDE_PLUGIN_DATA}. The plugin root is: ${CLAUDE_PLUGIN_ROOT}",
229
+ "timeout": 120
230
+ }
231
+ ]
232
+ }
233
+ ],
234
+ "SessionStart": [
235
+ {
236
+ "matcher": "startup",
237
+ "hooks": [
238
+ {
239
+ "type": "command",
240
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start-check",
241
+ "timeout": 5
242
+ }
243
+ ]
244
+ }
245
+ ]
246
+ }
247
+ }
248
+ ```
249
+
250
+ ## 6. package.json
251
+
252
+ ```json
253
+ {
254
+ "name": "@giwonn/claude-daily-review",
255
+ "version": "0.3.0",
256
+ "type": "module",
257
+ "description": "Claude Code plugin that auto-captures conversations for daily review and career documentation",
258
+ "repository": {
259
+ "type": "git",
260
+ "url": "https://github.com/giwonn/claude-daily-review"
261
+ },
262
+ "license": "MIT"
263
+ }
264
+ ```
265
+
266
+ 의존성 없음. devDependencies도 없음.
267
+
268
+ ## 7. marketplace.json SHA 자동 업데이트
269
+
270
+ ### 문제
271
+
272
+ 릴리즈마다 marketplace.json의 SHA를 수동으로 업데이트해야 함.
273
+
274
+ ### 해결: GitHub Actions 워크플로우
275
+
276
+ release 생성 시:
277
+ 1. npm publish
278
+ 2. marketplace.json의 SHA를 릴리즈 태그의 커밋 SHA로 업데이트
279
+ 3. 자동 커밋 + push
280
+
281
+ ```yaml
282
+ # .github/workflows/update-marketplace.yml
283
+ name: Update Marketplace SHA
284
+
285
+ on:
286
+ release:
287
+ types: [published]
288
+
289
+ jobs:
290
+ update-sha:
291
+ runs-on: ubuntu-latest
292
+ permissions:
293
+ contents: write
294
+ steps:
295
+ - uses: actions/checkout@v4
296
+ with:
297
+ ref: master
298
+
299
+ - name: Update SHA in marketplace.json
300
+ run: |
301
+ SHA=$(git rev-parse HEAD)
302
+ sed -i "s/\"sha\": \"[a-f0-9]*\"/\"sha\": \"$SHA\"/" .claude-plugin/marketplace.json
303
+
304
+ - name: Commit and push
305
+ run: |
306
+ git config user.name "github-actions[bot]"
307
+ git config user.email "github-actions[bot]@users.noreply.github.com"
308
+ git add .claude-plugin/marketplace.json
309
+ git commit -m "chore: update marketplace SHA to $SHA" || exit 0
310
+ git push
311
+ ```
312
+
313
+ ### 주의: 순환 방지
314
+
315
+ marketplace.json 변경 커밋은 릴리즈 태그에 포함되지 않으므로, 다음 릴리즈에서 반영됨. 이는 의도된 동작 — 릴리즈 시점의 코드가 설치되고, SHA는 그 직후 업데이트됨.
316
+
317
+ ## 8. 스코프
318
+
319
+ ### 포함
320
+
321
+ - TypeScript → .mjs + JSDoc 변환 (config, storage, github-storage, github-auth, periods)
322
+ - Hook 스크립트 변환 (on-stop.mjs, session-start-check bash)
323
+ - run-hook.cmd polyglot wrapper 추가
324
+ - hooks.json 업데이트
325
+ - package.json 최소화 (빌드 의존성 제거)
326
+ - src/, dist/, tests/, tsconfig, tsup, vitest 제거
327
+ - marketplace SHA 자동 업데이트 워크플로우
328
+ - .gitignore 정리
329
+
330
+ ### 제외
331
+
332
+ - 기능 변경 없음 — 동일한 동작, 다른 구조
333
+ - 테스트 프레임워크 재구축 (플러그인은 수동 테스트가 표준)
@@ -0,0 +1,365 @@
1
+ # StorageAdapter 추상화 + GitHub 연동 설계 문서
2
+
3
+ ## 1. 개요
4
+
5
+ 기존 claude-daily-review 플러그인의 외부 저장소 의존성(fs)을 `StorageAdapter` 인터페이스로 추상화하고, GitHub를 저장소 백엔드로 추가한다. 사용자는 셋업 시 로컬 파일시스템 또는 GitHub 중 선택할 수 있다.
6
+
7
+ ## 2. 목표
8
+
9
+ - 외부 저장소 의존성을 인터페이스로 추상화 (DIP)
10
+ - GitHub Contents API 기반 저장소 어댑터 추가
11
+ - GitHub OAuth Device Flow로 사용자 인증 (우리 OAuth App의 client_id 사용)
12
+ - GitHub repo 자동 생성 또는 기존 repo 지정 지원
13
+ - 기존 로컬 저장소 기능 유지
14
+ - 기존 테스트 async 전환 및 유지
15
+
16
+ ## 3. StorageAdapter 인터페이스
17
+
18
+ ```typescript
19
+ export interface StorageAdapter {
20
+ read(path: string): Promise<string | null>;
21
+ write(path: string, content: string): Promise<void>;
22
+ append(path: string, content: string): Promise<void>;
23
+ exists(path: string): Promise<boolean>;
24
+ list(dir: string): Promise<string[]>;
25
+ mkdir(dir: string): Promise<void>;
26
+ isDirectory(path: string): Promise<boolean>;
27
+ }
28
+ ```
29
+
30
+ 모든 경로는 storage root 기준 상대 경로. 예: `daily/2026-03-28.md`, `.raw/sess-1/2026-03-28.jsonl`
31
+
32
+ ## 4. 구현체
33
+
34
+ ### 4.1 LocalStorageAdapter
35
+
36
+ 기존 fs 코드를 StorageAdapter 인터페이스로 감싼다.
37
+
38
+ ```typescript
39
+ export class LocalStorageAdapter implements StorageAdapter {
40
+ constructor(private basePath: string) {}
41
+ // fs 함수들을 async wrapper로 구현
42
+ }
43
+ ```
44
+
45
+ | 메서드 | 구현 |
46
+ |--------|------|
47
+ | `read(path)` | `readFileSync(resolve(basePath, path))` — 파일 없으면 `null` 반환 |
48
+ | `write(path, content)` | `mkdirSync` + `writeFileSync` |
49
+ | `append(path, content)` | `mkdirSync` + `appendFileSync` |
50
+ | `exists(path)` | `existsSync` |
51
+ | `list(dir)` | `readdirSync` — 디렉토리 없으면 `[]` |
52
+ | `mkdir(dir)` | `mkdirSync({ recursive: true })` |
53
+ | `isDirectory(path)` | `statSync().isDirectory()` — 에러 시 `false` |
54
+
55
+ ### 4.2 GitHubStorageAdapter
56
+
57
+ GitHub Contents API를 사용한다.
58
+
59
+ ```typescript
60
+ export class GitHubStorageAdapter implements StorageAdapter {
61
+ constructor(private owner: string, private repo: string, private token: string, private basePath: string) {}
62
+ }
63
+ ```
64
+
65
+ | 메서드 | GitHub API |
66
+ |--------|------------|
67
+ | `read(path)` | `GET /repos/{owner}/{repo}/contents/{basePath}/{path}` — base64 디코드, 404면 `null` |
68
+ | `write(path, content)` | `PUT /repos/{owner}/{repo}/contents/{basePath}/{path}` — 기존 파일이면 SHA 포함 |
69
+ | `append(path, content)` | `read` → 기존 내용 + content → `write` (SHA 기반 업데이트) |
70
+ | `exists(path)` | `GET` 후 200이면 `true`, 404면 `false` |
71
+ | `list(dir)` | `GET /repos/{owner}/{repo}/contents/{basePath}/{dir}` — 배열 응답에서 name 추출 |
72
+ | `mkdir(dir)` | no-op (GitHub는 디렉토리 개념 없음, 파일 생성 시 자동 생성) |
73
+ | `isDirectory(path)` | `GET` 후 응답이 배열이면 `true` |
74
+
75
+ ### SHA 관리
76
+
77
+ GitHub Contents API는 파일 업데이트 시 현재 SHA가 필요하다. GitHubStorageAdapter 내부에서:
78
+ 1. `write`/`append` 호출 시 먼저 `GET`으로 현재 SHA 취득
79
+ 2. SHA와 함께 `PUT` 요청
80
+ 3. 409 Conflict 발생 시 SHA 재취득 후 재시도 (최대 3회)
81
+
82
+ ### Rate Limit 고려
83
+
84
+ - GitHub API: 인증된 요청 시간당 5,000회
85
+ - Stop 훅은 async라 응답마다 1회 호출 — 하루 수백 회 수준으로 충분
86
+ - 주기별 요약은 SessionStart에서 몇 회 호출 — 무시할 수준
87
+
88
+ ## 5. GitHub OAuth Device Flow
89
+
90
+ ### 흐름
91
+
92
+ ```
93
+ 1. POST https://github.com/login/device/code
94
+ Body: { client_id: "OUR_CLIENT_ID", scope: "repo" }
95
+ Response: { device_code, user_code, verification_uri, interval }
96
+
97
+ 2. 사용자에게 표시:
98
+ "https://github.com/login/device 에 접속해서 코드 ABCD-1234 를 입력하세요"
99
+
100
+ 3. Polling (interval 간격):
101
+ POST https://github.com/login/oauth/access_token
102
+ Body: { client_id, device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }
103
+
104
+ - "authorization_pending" → 계속 polling
105
+ - "slow_down" → interval 증가 후 계속
106
+ - 200 + access_token → 완료
107
+
108
+ 4. 토큰을 config에 저장
109
+ ```
110
+
111
+ ### client_id
112
+
113
+ 코드에 상수로 포함. OAuth App은 GitHub에 "claude-daily-review"로 등록.
114
+
115
+ ```typescript
116
+ const GITHUB_CLIENT_ID = "Ov23li..."; // 실제 등록 후 값
117
+ ```
118
+
119
+ 참고: Device Flow는 public client이므로 client_secret 불필요.
120
+
121
+ ### Scope
122
+
123
+ `repo` — private repo 접근 필요. public repo만 지원하려면 `public_repo`로 축소 가능하나, 사용자 편의를 위해 `repo` 사용.
124
+
125
+ ## 6. GitHub Repo 설정
126
+
127
+ ### 셋업 플로우
128
+
129
+ ```
130
+ /daily-review-setup
131
+ → storage 선택: "local" / "github"
132
+
133
+ [github 선택 시]
134
+ → OAuth Device Flow 인증
135
+ → "기존 repo를 사용할까요, 새로 만들까요?"
136
+ → 기존: repo 이름 입력 (owner/repo)
137
+ → 새로: repo 이름 입력 → POST /user/repos로 생성 (private)
138
+ → basePath 설정 (기본: "daily-review")
139
+ → 완료
140
+ ```
141
+
142
+ ### Repo 생성 API
143
+
144
+ ```
145
+ POST /user/repos
146
+ Body: {
147
+ name: "daily-review",
148
+ private: true,
149
+ description: "Auto-generated daily review by claude-daily-review"
150
+ }
151
+ ```
152
+
153
+ ## 7. 설정 스키마 변경
154
+
155
+ ### 기존
156
+
157
+ ```json
158
+ {
159
+ "vaultPath": "/path/to/vault",
160
+ "reviewFolder": "daily-review",
161
+ ...
162
+ }
163
+ ```
164
+
165
+ ### 변경 후
166
+
167
+ ```json
168
+ {
169
+ "storage": {
170
+ "type": "local",
171
+ "local": {
172
+ "basePath": "/path/to/vault/daily-review"
173
+ }
174
+ },
175
+ "language": "ko",
176
+ "periods": {
177
+ "daily": true,
178
+ "weekly": true,
179
+ "monthly": true,
180
+ "quarterly": true,
181
+ "yearly": false
182
+ },
183
+ "profile": {
184
+ "company": "ABC Corp",
185
+ "role": "프론트엔드 개발자",
186
+ "team": "결제플랫폼팀",
187
+ "context": "B2B SaaS 결제 시스템 개발 및 운영"
188
+ }
189
+ }
190
+ ```
191
+
192
+ 또는 GitHub:
193
+
194
+ ```json
195
+ {
196
+ "storage": {
197
+ "type": "github",
198
+ "github": {
199
+ "owner": "username",
200
+ "repo": "daily-review",
201
+ "token": "gho_xxx",
202
+ "basePath": "daily-review"
203
+ }
204
+ },
205
+ "language": "ko",
206
+ "periods": { ... },
207
+ "profile": { ... }
208
+ }
209
+ ```
210
+
211
+ `vaultPath`와 `reviewFolder`는 `storage.local.basePath`로 통합. 기존 config가 있으면 마이그레이션 지원.
212
+
213
+ ## 8. 변경 대상 모듈
214
+
215
+ ### 변경 필요
216
+
217
+ | 모듈 | 변경 내용 |
218
+ |------|-----------|
219
+ | `src/core/storage.ts` | **신규.** StorageAdapter 인터페이스 정의 |
220
+ | `src/core/local-storage.ts` | **신규.** LocalStorageAdapter 구현 |
221
+ | `src/core/github-storage.ts` | **신규.** GitHubStorageAdapter 구현 |
222
+ | `src/core/github-auth.ts` | **신규.** OAuth Device Flow 구현 |
223
+ | `src/core/config.ts` | 설정 스키마 변경 + 어댑터 팩토리 함수 |
224
+ | `src/core/vault.ts` | fs 직접 사용 → StorageAdapter 주입, async 전환 |
225
+ | `src/core/raw-logger.ts` | fs 직접 사용 → StorageAdapter 주입, async 전환 |
226
+ | `src/core/merge.ts` | fs 직접 사용 → StorageAdapter 주입, async 전환 |
227
+ | `src/hooks/on-stop.ts` | adapter 생성 후 주입, async 전환 |
228
+ | `prompts/session-end.md` | 에이전트가 storage type에 따라 다르게 동작하도록 안내 |
229
+ | `prompts/session-start.md` | 동일 |
230
+ | `skills/daily-review-setup.md` | storage 선택 + GitHub 인증 플로우 추가 |
231
+
232
+ ### 변경 없음
233
+
234
+ | 모듈 | 이유 |
235
+ |------|------|
236
+ | `src/core/periods.ts` | 순수 계산, 외부 의존 없음 |
237
+ | `hooks/hooks.json` | 훅 설정 자체는 동일 |
238
+
239
+ ## 9. async 전환
240
+
241
+ StorageAdapter의 모든 메서드가 `Promise`를 반환하므로, 이를 사용하는 모든 함수가 async로 전환된다.
242
+
243
+ ### 영향 범위
244
+
245
+ ```typescript
246
+ // Before (sync)
247
+ export function appendRawLog(sessionDir: string, date: string, entry: HookInput): void
248
+
249
+ // After (async)
250
+ export async function appendRawLog(storage: StorageAdapter, sessionDir: string, date: string, entry: HookInput): Promise<void>
251
+ ```
252
+
253
+ | 함수 | 변경 |
254
+ |------|------|
255
+ | `vault.ensureVaultDirectories(config)` | `ensureVaultDirectories(storage, config)` async |
256
+ | `rawLogger.appendRawLog(dir, date, entry)` | `appendRawLog(storage, dir, date, entry)` async |
257
+ | `merge.findUnprocessedSessions(dir)` | `findUnprocessedSessions(storage, dir)` async |
258
+ | `merge.findPendingReviews(dir)` | `findPendingReviews(storage, dir)` async |
259
+ | `merge.markSessionCompleted(dir)` | `markSessionCompleted(storage, dir)` async |
260
+ | `merge.isSessionCompleted(dir)` | `isSessionCompleted(storage, dir)` async |
261
+ | `merge.mergeReviewsIntoDaily(paths, daily)` | `mergeReviewsIntoDaily(storage, paths, daily)` async |
262
+ | `onStop.handleStopHook(stdin)` | `handleStopHook(stdin)` async |
263
+
264
+ vault의 경로 생성 함수들(getDailyPath 등)은 순수 문자열 연산이므로 변경 없음.
265
+
266
+ ## 10. 테스트 전략
267
+
268
+ ### LocalStorageAdapter 테스트
269
+
270
+ 기존 테스트를 async로 전환 + StorageAdapter를 통해 호출하도록 수정. 실질적 동작은 동일.
271
+
272
+ ### GitHubStorageAdapter 테스트
273
+
274
+ GitHub API를 직접 호출하지 않고, HTTP 요청을 mock하여 테스트.
275
+
276
+ ```typescript
277
+ // fetch를 mock하여 GitHub API 응답 시뮬레이션
278
+ vi.spyOn(globalThis, "fetch").mockImplementation(...)
279
+ ```
280
+
281
+ 테스트 케이스:
282
+ - read: 정상 응답 (base64 디코드), 404 → null
283
+ - write: 새 파일 생성 (SHA 없음), 기존 파일 업데이트 (SHA 포함)
284
+ - append: read + write 조합
285
+ - exists: 200 → true, 404 → false
286
+ - list: 배열 응답에서 name 추출
287
+ - write 409 conflict → SHA 재취득 후 재시도
288
+
289
+ ### GitHub Auth 테스트
290
+
291
+ Device Flow의 각 단계를 fetch mock으로 테스트:
292
+ - device code 요청 성공
293
+ - polling: authorization_pending → 재시도
294
+ - polling: slow_down → interval 증가
295
+ - polling: 성공 → 토큰 반환
296
+ - polling: 타임아웃
297
+
298
+ ### 코어 모듈 테스트
299
+
300
+ vault, raw-logger, merge 테스트는 StorageAdapter mock을 주입하여 테스트. 실제 fs 의존 없이 순수 로직만 검증.
301
+
302
+ 추가로 LocalStorageAdapter를 사용한 통합 테스트도 유지.
303
+
304
+ ## 11. 에이전트 프롬프트 변경
305
+
306
+ SessionEnd/SessionStart 에이전트는 파일을 직접 Read/Write 도구로 조작한다. GitHub storage인 경우:
307
+ - 에이전트가 config에서 storage type 확인
308
+ - `type: "local"` → 기존대로 Read/Write 도구 사용
309
+ - `type: "github"` → Bash 도구로 `node` 스크립트 호출하여 GitHub API 사용
310
+
311
+ 이를 위해 에이전트가 호출할 수 있는 CLI 유틸리티 스크립트를 추가:
312
+
313
+ ```bash
314
+ # 파일 읽기
315
+ node dist/cli/storage-read.js <path>
316
+
317
+ # 파일 쓰기 (stdin으로 content)
318
+ echo "content" | node dist/cli/storage-write.js <path>
319
+
320
+ # 파일 목록
321
+ node dist/cli/storage-list.js <dir>
322
+ ```
323
+
324
+ 이 스크립트들은 config에서 storage type을 읽고 적절한 어댑터를 사용한다.
325
+
326
+ ## 12. Config 마이그레이션
327
+
328
+ 기존 config 형식(`vaultPath` + `reviewFolder`)이 감지되면 자동 마이그레이션:
329
+
330
+ ```typescript
331
+ function migrateConfig(old: OldConfig): Config {
332
+ return {
333
+ storage: {
334
+ type: "local",
335
+ local: {
336
+ basePath: join(old.vaultPath, old.reviewFolder),
337
+ },
338
+ },
339
+ language: old.language,
340
+ periods: old.periods,
341
+ profile: old.profile,
342
+ };
343
+ }
344
+ ```
345
+
346
+ ## 13. 스코프
347
+
348
+ ### 이번 스코프
349
+
350
+ - StorageAdapter 인터페이스 정의
351
+ - LocalStorageAdapter 구현
352
+ - GitHubStorageAdapter 구현
353
+ - GitHub OAuth Device Flow 구현
354
+ - 코어 모듈 리팩토링 (StorageAdapter 주입, async 전환)
355
+ - 에이전트용 CLI 스크립트 (storage-read, storage-write, storage-list)
356
+ - 설정 스키마 변경 + 마이그레이션
357
+ - 셋업 스킬 업데이트 (storage 선택 + GitHub 인증)
358
+ - 에이전트 프롬프트 업데이트
359
+ - 테스트 전면 수정
360
+
361
+ ### 제외
362
+
363
+ - GitHub App 서버 사이드 (불필요 — Device Flow는 serverless)
364
+ - 실제 OAuth App 등록 (구현 후 별도 진행)
365
+ - GitHub Pages 연동