claude-context-auto-handoff 1.0.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/.claude-plugin/plugin.json +18 -0
- package/LICENSE +21 -0
- package/README.ko.md +190 -0
- package/README.md +190 -0
- package/build/index.js +104 -0
- package/hooks/hooks.json +34 -0
- package/package.json +36 -0
- package/scripts/setup.js +12 -0
- package/scripts/setup.ps1 +38 -0
- package/scripts/setup.sh +37 -0
- package/templates/.codex/config.toml +2 -0
- package/templates/.codex/hooks.json +43 -0
- package/templates/AGENTS.md +33 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-context-handoff",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatically saves session context and generates handoff manifests before Claude compacts or stops.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Ethualo",
|
|
7
|
+
"email": "lkh0926ym@naver.com"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"claude": "^1.0.0"
|
|
11
|
+
},
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"context-handoff-manager": {
|
|
14
|
+
"command": "node",
|
|
15
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/build/index.js"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ethualo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# claude-context-auto-handoff
|
|
2
|
+
|
|
3
|
+
**[English](README.md)** | 한국어
|
|
4
|
+
|
|
5
|
+
컨텍스트 압축 또는 세션 종료 직전, 다음 세션이 현재 맥락을 그대로 이어받을 수 있도록 토큰 효율적인 핸드오프 문서를 자동으로 작성하는 Claude Code 플러그인.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 개요
|
|
14
|
+
|
|
15
|
+
Claude의 컨텍스트 창이 가득 차면 대화가 자동으로 압축됩니다. 이 시점에 내린 설계 결정, 해결 중이던 버그, 다음 할 일이 흐릿해집니다. 이 플러그인은 `PreCompact`와 `Stop` 이벤트를 감지해 압축 전에 AI가 직접 핸드오프 문서를 작성하도록 합니다. 다음 세션은 그 파일 하나로 즉시 이어집니다.
|
|
16
|
+
|
|
17
|
+
핸드오프 내용은 **텔레그래피즘** (관사·군더더기·코드 스니펫 없음) 으로 작성되어 토큰 효율을 극대화하면서도 다음 세션에 필요한 모든 결정 맥락을 보존합니다.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 구성 요소
|
|
22
|
+
|
|
23
|
+
### 도구 (Tools)
|
|
24
|
+
|
|
25
|
+
- **`generate_handoff_manifest`** — 현재 프로젝트의 `.claude/handoff.md`에 구조화된 핸드오프 문서를 저장합니다. `.claude/handoffs/handoff-{timestamp}.md`에도 아카이브됩니다.
|
|
26
|
+
|
|
27
|
+
| 파라미터 | 타입 | 필수 | 설명 |
|
|
28
|
+
|----------|------|------|------|
|
|
29
|
+
| `summary` | `string` | ✅ | 간결한 세션 요약 (텔레그래피즘) |
|
|
30
|
+
| `nextSteps` | `string[]` | ✅ | 다음 세션에서 즉시 이어받을 할 일 목록 |
|
|
31
|
+
| `taskDescription` | `string` | ✗ | 상위 목표 + 핵심 의도 (왜 중요한가) |
|
|
32
|
+
| `currentStatus` | `string` | ✗ | 완료된 것 vs 남은 것 — 무엇이 아닌 왜를 명시 |
|
|
33
|
+
| `keyDecisions` | `string[]` | ✗ | 아키텍처 결정과 이유. 형식: `"Decision: X — Reason: Y"` |
|
|
34
|
+
| `failedApproaches` | `string[]` | ✗ | 이미 실패한 시도. 형식: `"Approach: X → Result: Y → Lesson: Z"` |
|
|
35
|
+
| `modifiedFiles` | `string[]` | ✗ | 변경 파일과 델타 메모. 형식: `"경로/파일: 무엇을 변경"` — 코드 금지 |
|
|
36
|
+
| `implicitRules` | `string[]` | ✗ | 기술 스택, 네이밍 컨벤션, 환경변수 — 코드에서 유추 불가한 규칙 |
|
|
37
|
+
| `blockers` | `string` | ✗ | 미해결 에러 또는 막혀 있는 문제 |
|
|
38
|
+
|
|
39
|
+
### 스킬 (Skills)
|
|
40
|
+
|
|
41
|
+
| 명령어 | 동작 |
|
|
42
|
+
|--------|------|
|
|
43
|
+
| `/handoff` | 세션 컨텍스트 수집 후 `generate_handoff_manifest` 호출 |
|
|
44
|
+
| `/resume` | `.claude/handoff.md` 읽어 새 세션에서 컨텍스트 복원 |
|
|
45
|
+
|
|
46
|
+
### 훅 (Hooks)
|
|
47
|
+
|
|
48
|
+
Claude Code 훅은 기본 내장. Codex 훅은 `templates/.codex`를 프로젝트 루트에 복사해야 활성화됩니다 ([Codex 설치](#codex) 참조).
|
|
49
|
+
|
|
50
|
+
| 이벤트 | 동작 |
|
|
51
|
+
|--------|------|
|
|
52
|
+
| `PreCompact` | 컨텍스트 압축 직전 `generate_handoff_manifest` 호출 지시 |
|
|
53
|
+
| `Stop` | 핸드오프 파일이 오래됐거나 없으면 경고 |
|
|
54
|
+
| `SessionStart` | `.claude/handoff.md` 존재 시 자동으로 컨텍스트 복원 |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 빠른 시작
|
|
59
|
+
|
|
60
|
+
**Linux / macOS**
|
|
61
|
+
```bash
|
|
62
|
+
curl -fsSL https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.sh | bash
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Windows (PowerShell)**
|
|
66
|
+
```powershell
|
|
67
|
+
irm https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.ps1 | iex
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**npm (크로스플랫폼)**
|
|
71
|
+
```bash
|
|
72
|
+
npm install -g claude-context-auto-handoff
|
|
73
|
+
claude-context-handoff-setup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 설치
|
|
79
|
+
|
|
80
|
+
### Claude Code 플러그인으로 설치
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude plugin install claude-context-auto-handoff
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### npm 패키지로 설치
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g claude-context-auto-handoff
|
|
90
|
+
npm run setup # hooks.json 자동 배치
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### MCP 수동 설정 (Claude Code)
|
|
94
|
+
|
|
95
|
+
Claude Code `settings.json`에 추가:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"context-handoff-manager": {
|
|
101
|
+
"command": "node",
|
|
102
|
+
"args": ["/path/to/build/index.js"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Codex
|
|
109
|
+
|
|
110
|
+
`~/.codex/config.toml`에 추가 (전역 MCP 설정):
|
|
111
|
+
|
|
112
|
+
```toml
|
|
113
|
+
[mcp_servers.context-handoff]
|
|
114
|
+
command = "node"
|
|
115
|
+
args = ["/path/to/build/index.js"]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
그런 다음 훅 템플릿을 프로젝트 루트에 복사:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cp -r /path/to/claude-context-auto-handoff/templates/.codex ./.codex
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Claude Code와 동일한 `SessionStart`, `PreCompact`, `Stop` 훅이 활성화됩니다.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 사용법
|
|
129
|
+
|
|
130
|
+
### Claude Code
|
|
131
|
+
|
|
132
|
+
세 훅 모두 자동 실행 — `SessionStart`는 세션 시작 시 컨텍스트 복원, `PreCompact`는 압축 전 저장, `Stop`은 핸드오프가 오래됐을 때 경고. 생성된 문서는 `.claude/handoff.md`에 저장됩니다.
|
|
133
|
+
|
|
134
|
+
**수동 체크포인트:**
|
|
135
|
+
```
|
|
136
|
+
/handoff
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**수동 복원 (자동 복원이 실행되지 않은 경우):**
|
|
140
|
+
```
|
|
141
|
+
/resume
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Codex
|
|
145
|
+
|
|
146
|
+
동일한 세 훅이 `.codex/hooks.json`을 통해 자동 실행됩니다. 슬래시 명령어 없음 — 훅이 모두 처리.
|
|
147
|
+
|
|
148
|
+
| 이벤트 | 동작 |
|
|
149
|
+
|--------|------|
|
|
150
|
+
| `SessionStart` | `.claude/handoff.md`를 읽어 컨텍스트로 주입 |
|
|
151
|
+
| `PreCompact` | 압축 전 `generate_handoff_manifest` 호출 지시 |
|
|
152
|
+
| `Stop` | 핸드오프가 오래됐거나 없으면 경고 |
|
|
153
|
+
|
|
154
|
+
### 출력 형식
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
# Session Handoff Snapshot
|
|
158
|
+
> **Generated:** 2026. 6. 22. 오후 3:30:00
|
|
159
|
+
|
|
160
|
+
## 🎯 High-Level Objective
|
|
161
|
+
* **Goal:** Supabase + Notion 주식 데이터 실시간 동기화 Next.js 15 앱 구축
|
|
162
|
+
* **Core Intent:** Zustand 스토어로 클라이언트 재요청 최소화 — 비용 절감
|
|
163
|
+
|
|
164
|
+
## 📌 Current State & Next Steps
|
|
165
|
+
* **Status:** Task 3 (Zustand 스토어) 완료
|
|
166
|
+
* **Blocker:** Notion API rate limit (3 req/s) — 버퍼 레이어 필요
|
|
167
|
+
* **Next Action:** Supabase Edge Functions 디바운스 큐 구현
|
|
168
|
+
|
|
169
|
+
## 🛠️ Modified Files Delta
|
|
170
|
+
* src/store/stockStore.ts: Zustand 스토어 뼈대 + syncStatus 상태 정의
|
|
171
|
+
* src/app/api/notion/sync/route.ts: POST 핸들러 작성 완료, Supabase 미연결
|
|
172
|
+
|
|
173
|
+
## 🚫 Failed Approaches (DO NOT RETRY)
|
|
174
|
+
* Approach: Server Actions에서 Notion API 직접 호출 → Result: 리렌더 시 rate limit 즉시 터짐 → Lesson: 큐 미들웨어 필수
|
|
175
|
+
* Approach: useEffect 주기 폴링 → Result: Supabase 읽기 사용량 급증 → Lesson: 폐기
|
|
176
|
+
|
|
177
|
+
## 🔑 Crucial Context & Implicit Rules
|
|
178
|
+
* Stack: Next.js 15 (App Router), Supabase v2, Zustand v5
|
|
179
|
+
* Naming: API 엔드포인트 항상 route.ts, 스토어 PascalCase
|
|
180
|
+
* Env: NEXT_PUBLIC_SUPABASE_ANON_KEY 사용 중
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
*다음 세션 시작 시 자동으로 컨텍스트가 복원됩니다. 수동 복원: `/resume`*
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 라이선스
|
|
189
|
+
|
|
190
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# claude-context-auto-handoff
|
|
2
|
+
|
|
3
|
+
English | **[한국어](README.ko.md)**
|
|
4
|
+
|
|
5
|
+
Claude Code plugin that automatically saves session context and generates token-efficient handoff manifests before Claude compacts or stops.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Claude's context window eventually fills and compacts — losing design decisions, active blockers, and next steps mid-session. This plugin hooks into `PreCompact` and `Stop` events to trigger an AI-authored handoff document before that happens, so the next session picks up exactly where this one left off.
|
|
16
|
+
|
|
17
|
+
Handoff content is written in **telegraphese** (no articles, no filler, no code snippets) and structured to maximize token efficiency while preserving all decision context the next session needs.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Components
|
|
22
|
+
|
|
23
|
+
### Tools
|
|
24
|
+
|
|
25
|
+
- **`generate_handoff_manifest`** — Writes a structured `.claude/handoff.md` to the current project directory. Also archives to `.claude/handoffs/handoff-{timestamp}.md`.
|
|
26
|
+
|
|
27
|
+
| Parameter | Type | Required | Description |
|
|
28
|
+
|-----------|------|----------|-------------|
|
|
29
|
+
| `summary` | `string` | ✅ | Terse session recap (telegraphese) |
|
|
30
|
+
| `nextSteps` | `string[]` | ✅ | Ordered todo list for the next session |
|
|
31
|
+
| `taskDescription` | `string` | ✗ | High-level goal + core intent (why this matters) |
|
|
32
|
+
| `currentStatus` | `string` | ✗ | What is done vs what remains — state why, not just what |
|
|
33
|
+
| `keyDecisions` | `string[]` | ✗ | Architecture choices and reasons. Format: `"Decision: X — Reason: Y"` |
|
|
34
|
+
| `failedApproaches` | `string[]` | ✗ | Already-failed attempts. Format: `"Approach: X → Result: Y → Lesson: Z"` |
|
|
35
|
+
| `modifiedFiles` | `string[]` | ✗ | Changed files with delta notes. Format: `"path/to/file: what changed"` — no code |
|
|
36
|
+
| `implicitRules` | `string[]` | ✗ | Tech stack, naming conventions, env vars — anything not derivable from reading code |
|
|
37
|
+
| `blockers` | `string` | ✗ | Unresolved errors or open questions |
|
|
38
|
+
|
|
39
|
+
### Skills
|
|
40
|
+
|
|
41
|
+
| Command | Behavior |
|
|
42
|
+
|---------|----------|
|
|
43
|
+
| `/handoff` | Gather session context and call `generate_handoff_manifest` |
|
|
44
|
+
| `/resume` | Read `.claude/handoff.md` and restore context in a new session |
|
|
45
|
+
|
|
46
|
+
### Hooks
|
|
47
|
+
|
|
48
|
+
Claude Code hooks are built-in. Codex hooks require copying `templates/.codex` to your project root (see [Codex installation](#codex)).
|
|
49
|
+
|
|
50
|
+
| Event | Behavior |
|
|
51
|
+
|-------|----------|
|
|
52
|
+
| `PreCompact` | Prompts the model to call `generate_handoff_manifest` before context compression |
|
|
53
|
+
| `Stop` | Warns if handoff is stale or missing after each response |
|
|
54
|
+
| `SessionStart` | Auto-restores context from `.claude/handoff.md` if it exists |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
**Linux / macOS**
|
|
61
|
+
```bash
|
|
62
|
+
curl -fsSL https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.sh | bash
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Windows (PowerShell)**
|
|
66
|
+
```powershell
|
|
67
|
+
irm https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.ps1 | iex
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**npm (cross-platform)**
|
|
71
|
+
```bash
|
|
72
|
+
npm install -g claude-context-auto-handoff
|
|
73
|
+
claude-context-handoff-setup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
### As a Claude Code plugin
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude plugin install claude-context-auto-handoff
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### As an npm package
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g claude-context-auto-handoff
|
|
90
|
+
npm run setup # hooks.json 자동 배치
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Manual MCP configuration (Claude Code)
|
|
94
|
+
|
|
95
|
+
Add to your Claude Code `settings.json`:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"context-handoff-manager": {
|
|
101
|
+
"command": "node",
|
|
102
|
+
"args": ["/path/to/build/index.js"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Codex
|
|
109
|
+
|
|
110
|
+
Add to `~/.codex/config.toml` (global MCP config):
|
|
111
|
+
|
|
112
|
+
```toml
|
|
113
|
+
[mcp_servers.context-handoff]
|
|
114
|
+
command = "node"
|
|
115
|
+
args = ["/path/to/build/index.js"]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then copy the hook templates to your project root:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cp -r /path/to/claude-context-auto-handoff/templates/.codex ./.codex
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This enables the same `SessionStart`, `PreCompact`, and `Stop` hooks as Claude Code.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Usage
|
|
129
|
+
|
|
130
|
+
### Claude Code
|
|
131
|
+
|
|
132
|
+
All three hooks fire automatically — `SessionStart` restores context on session open, `PreCompact` saves before compression, `Stop` warns if handoff is stale. Generated manifests are saved to `.claude/handoff.md`.
|
|
133
|
+
|
|
134
|
+
**Manual checkpoint:**
|
|
135
|
+
```
|
|
136
|
+
/handoff
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Manual resume (if auto-restore didn't trigger):**
|
|
140
|
+
```
|
|
141
|
+
/resume
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Codex
|
|
145
|
+
|
|
146
|
+
Same three hooks fire automatically via `.codex/hooks.json`. No slash commands — hooks handle everything.
|
|
147
|
+
|
|
148
|
+
| Event | Behavior |
|
|
149
|
+
|-------|----------|
|
|
150
|
+
| `SessionStart` | Reads `.claude/handoff.md` and injects content as context |
|
|
151
|
+
| `PreCompact` | Prompts Codex to call `generate_handoff_manifest` before compression |
|
|
152
|
+
| `Stop` | Warns if handoff is stale (>5 min) or missing |
|
|
153
|
+
|
|
154
|
+
### Output format
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
# Session Handoff Snapshot
|
|
158
|
+
> **Generated:** 6/22/2026, 3:30:00 PM
|
|
159
|
+
|
|
160
|
+
## 🎯 High-Level Objective
|
|
161
|
+
* **Goal:** Build Next.js 15 app syncing Supabase + Notion stock data in real-time
|
|
162
|
+
* **Core Intent:** Minimize client re-fetches via Zustand store — cost control
|
|
163
|
+
|
|
164
|
+
## 📌 Current State & Next Steps
|
|
165
|
+
* **Status:** Task 3 (Zustand store) complete
|
|
166
|
+
* **Blocker:** Notion API rate limit (3 req/s) — buffer layer needed
|
|
167
|
+
* **Next Action:** Implement Supabase Edge Functions debounce queue
|
|
168
|
+
|
|
169
|
+
## 🛠️ Modified Files Delta
|
|
170
|
+
* src/store/stockStore.ts: Zustand store skeleton + syncStatus state
|
|
171
|
+
* src/app/api/notion/sync/route.ts: POST handler written, Supabase not wired yet
|
|
172
|
+
|
|
173
|
+
## 🚫 Failed Approaches (DO NOT RETRY)
|
|
174
|
+
* Approach: Call Notion API directly from Server Actions → Result: Rate limit hit on re-render → Lesson: Queue middleware mandatory
|
|
175
|
+
* Approach: useEffect polling → Result: Supabase read usage spike → Lesson: Abandoned
|
|
176
|
+
|
|
177
|
+
## 🔑 Crucial Context & Implicit Rules
|
|
178
|
+
* Stack: Next.js 15 (App Router), Supabase v2, Zustand v5
|
|
179
|
+
* Naming: API endpoints always route.ts, PascalCase store names
|
|
180
|
+
* Env: NEXT_PUBLIC_SUPABASE_ANON_KEY active
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
*Run `/resume` in the next session to restore this context.*
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
package/build/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: 'context-handoff-manager',
|
|
9
|
+
version: '1.0.0'
|
|
10
|
+
});
|
|
11
|
+
const CONTEXT_THRESHOLD = parseInt(process.env.CONTEXT_THRESHOLD ?? '70', 10);
|
|
12
|
+
server.tool('generate_handoff_manifest', {
|
|
13
|
+
summary: z.string().optional().describe('Detailed session recap — omit if other fields cover it'),
|
|
14
|
+
nextSteps: z.array(z.string()).describe('Tasks to continue immediately in the next session'),
|
|
15
|
+
taskDescription: z.string().optional().describe('High-level goal + core intent (why this matters). Use telegraphese — drop articles/pronouns.'),
|
|
16
|
+
currentStatus: z.string().optional().describe('What is done vs what remains. State why, not just what.'),
|
|
17
|
+
keyDecisions: z.array(z.string()).optional().describe('Architecture choices and why — prevents post-compaction amnesia. Format: "Decision: X — Reason: Y"'),
|
|
18
|
+
failedApproaches: z.array(z.string()).optional().describe('Already-failed attempts. Format each: "Approach: X → Result: Y → Lesson: Z". Prevents repeating mistakes.'),
|
|
19
|
+
blockers: z.string().optional().describe('Unresolved errors or blockers'),
|
|
20
|
+
modifiedFiles: z.array(z.string()).optional().describe('Changed files with delta notes. Format: "path/to/file: what changed" — NO code snippets, path+delta only.'),
|
|
21
|
+
implicitRules: z.array(z.string()).optional().describe('Tech stack, naming conventions, env vars, implicit project rules — anything not derivable from reading code')
|
|
22
|
+
}, async ({ summary, nextSteps, taskDescription, currentStatus, keyDecisions, failedApproaches, blockers, modifiedFiles, implicitRules }) => {
|
|
23
|
+
try {
|
|
24
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
25
|
+
const handoffsDir = path.join(claudeDir, 'handoffs');
|
|
26
|
+
if (!fs.existsSync(claudeDir))
|
|
27
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
28
|
+
if (!fs.existsSync(handoffsDir))
|
|
29
|
+
fs.mkdirSync(handoffsDir, { recursive: true });
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const displayTime = now.toLocaleString();
|
|
32
|
+
const timestamp = now.toISOString().replace(/[:.]/g, '-');
|
|
33
|
+
const content = buildMarkdown({ summary, nextSteps, taskDescription, currentStatus, keyDecisions, failedApproaches, blockers, modifiedFiles, implicitRules, displayTime });
|
|
34
|
+
const mainPath = path.join(claudeDir, 'handoff.md');
|
|
35
|
+
fs.writeFileSync(mainPath, content, 'utf-8');
|
|
36
|
+
const archivePath = path.join(handoffsDir, `handoff-${timestamp}.md`);
|
|
37
|
+
fs.writeFileSync(archivePath, content, 'utf-8');
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: `Handoff saved.\nLatest: ${mainPath}\nArchive: ${archivePath}\n[Handoff Guard] threshold: ${CONTEXT_THRESHOLD}% | run /resume in next session`
|
|
42
|
+
}]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
isError: true,
|
|
48
|
+
content: [{ type: 'text', text: `Save error: ${error.message}` }]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
function buildMarkdown(params) {
|
|
53
|
+
const { summary, nextSteps, taskDescription, currentStatus, keyDecisions, failedApproaches, blockers, modifiedFiles, implicitRules, displayTime } = params;
|
|
54
|
+
const sections = [
|
|
55
|
+
`# Session Handoff Snapshot`,
|
|
56
|
+
`> **Generated:** ${displayTime}`,
|
|
57
|
+
``
|
|
58
|
+
];
|
|
59
|
+
if (taskDescription) {
|
|
60
|
+
sections.push(`## 🎯 High-Level Objective\n* **Goal:** ${taskDescription}\n`);
|
|
61
|
+
}
|
|
62
|
+
const stateLines = [];
|
|
63
|
+
if (currentStatus)
|
|
64
|
+
stateLines.push(`* **Status:** ${currentStatus}`);
|
|
65
|
+
if (blockers)
|
|
66
|
+
stateLines.push(`* **Blocker:** ${blockers}`);
|
|
67
|
+
if (nextSteps.length > 0)
|
|
68
|
+
stateLines.push(`* **Next Action:** ${nextSteps[0]}`);
|
|
69
|
+
if (stateLines.length > 0) {
|
|
70
|
+
sections.push(`## 📌 Current State & Next Steps\n${stateLines.join('\n')}\n`);
|
|
71
|
+
if (nextSteps.length > 1) {
|
|
72
|
+
sections.push(`### Remaining Queue\n${nextSteps.slice(1).map(s => `- [ ] ${s}`).join('\n')}\n`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
sections.push(`## 📌 Next Steps\n${nextSteps.map(s => `- [ ] ${s}`).join('\n')}\n`);
|
|
77
|
+
}
|
|
78
|
+
if (modifiedFiles && modifiedFiles.length > 0) {
|
|
79
|
+
sections.push(`## 🛠️ Modified Files Delta\n${modifiedFiles.map(f => `* ${f}`).join('\n')}\n`);
|
|
80
|
+
}
|
|
81
|
+
if (failedApproaches && failedApproaches.length > 0) {
|
|
82
|
+
sections.push(`## 🚫 Failed Approaches (DO NOT RETRY)\n${failedApproaches.map(f => `* ${f}`).join('\n')}\n`);
|
|
83
|
+
}
|
|
84
|
+
if (implicitRules && implicitRules.length > 0) {
|
|
85
|
+
sections.push(`## 🔑 Crucial Context & Implicit Rules\n${implicitRules.map(r => `* ${r}`).join('\n')}\n`);
|
|
86
|
+
}
|
|
87
|
+
if (keyDecisions && keyDecisions.length > 0) {
|
|
88
|
+
sections.push(`## Key Decisions\n${keyDecisions.map(d => `- ${d}`).join('\n')}\n`);
|
|
89
|
+
}
|
|
90
|
+
if (summary) {
|
|
91
|
+
sections.push(`## Summary\n${summary}\n`);
|
|
92
|
+
}
|
|
93
|
+
sections.push(`---\n*Run \`/resume\` in the next session to restore this context.*`);
|
|
94
|
+
return sections.join('\n');
|
|
95
|
+
}
|
|
96
|
+
async function main() {
|
|
97
|
+
const transport = new StdioServerTransport();
|
|
98
|
+
await server.connect(transport);
|
|
99
|
+
console.error('Context Handoff MCP Server running on stdio');
|
|
100
|
+
}
|
|
101
|
+
main().catch((error) => {
|
|
102
|
+
console.error('Fatal error:', error);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreCompact": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "prompt",
|
|
8
|
+
"prompt": "CRITICAL: Context compaction imminent. Call 'generate_handoff_manifest' NOW before compaction. Fill all fields: summary (full session recap), taskDescription (goal), currentStatus (done vs remaining), keyDecisions (architecture choices + why), failedApproaches (already-failed — do NOT retry), nextSteps (next session tasks), blockers (unresolved errors). Do NOT allow compaction without calling this tool."
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"Stop": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "prompt",
|
|
18
|
+
"prompt": "If a work unit just completed (feature done, tests passed, commit made), call 'generate_handoff_manifest'. Fill taskDescription, keyDecisions, and failedApproaches so the next session has full context."
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"SessionStart": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "prompt",
|
|
28
|
+
"prompt": "On session start: if .claude/handoff.md exists, read it and briefly brief the user (in their language) on Task Description, Current Status, Key Decisions, Failed Approaches, Blockers, Next Steps. If absent, stay silent."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-context-auto-handoff",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Claude Code plugin to automate session handoffs before context compaction.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-context-handoff": "build/index.js",
|
|
9
|
+
"claude-context-handoff-setup": "scripts/setup.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"build",
|
|
13
|
+
".claude-plugin",
|
|
14
|
+
"hooks",
|
|
15
|
+
"templates",
|
|
16
|
+
"scripts",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"setup": "node scripts/setup.js",
|
|
24
|
+
"setup:unix": "bash scripts/setup.sh",
|
|
25
|
+
"setup:win": "powershell -ExecutionPolicy Bypass -File scripts/setup.ps1"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
|
+
"zod": "^3.23.8"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.11.0",
|
|
34
|
+
"typescript": "^5.3.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
if (process.platform === 'win32') {
|
|
9
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${join(__dirname, 'setup.ps1')}"`, { stdio: 'inherit' });
|
|
10
|
+
} else {
|
|
11
|
+
execSync(`bash "${join(__dirname, 'setup.sh')}"`, { stdio: 'inherit' });
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# context-handoff-mcp-server — one-line setup for Windows (PowerShell)
|
|
2
|
+
# Usage: irm https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.ps1 | iex
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
|
|
6
|
+
$PluginName = "claude-context-handoff"
|
|
7
|
+
$HooksTarget = "$env:USERPROFILE\.claude\hooks.json"
|
|
8
|
+
|
|
9
|
+
Write-Host "[Handoff Setup] Installing $PluginName..."
|
|
10
|
+
|
|
11
|
+
# 1. npm global install (builds TypeScript)
|
|
12
|
+
npm install -g claude-context-auto-handoff
|
|
13
|
+
|
|
14
|
+
# 2. hooks.json 등록 (기존 파일이 있으면 경고만)
|
|
15
|
+
$NpmRoot = (npm root -g).Trim()
|
|
16
|
+
$HooksSrc = Join-Path $NpmRoot "claude-context-auto-handoff\hooks\hooks.json"
|
|
17
|
+
|
|
18
|
+
if (Test-Path $HooksTarget) {
|
|
19
|
+
Write-Host "[Handoff Setup] WARNING: $HooksTarget 이미 존재합니다."
|
|
20
|
+
Write-Host " 수동으로 $HooksSrc 내용을 병합하세요."
|
|
21
|
+
} else {
|
|
22
|
+
$HooksDir = Split-Path $HooksTarget
|
|
23
|
+
if (-not (Test-Path $HooksDir)) { New-Item -ItemType Directory -Force $HooksDir | Out-Null }
|
|
24
|
+
Copy-Item $HooksSrc $HooksTarget
|
|
25
|
+
Write-Host "[Handoff Setup] hooks.json 등록 완료: $HooksTarget"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Write-Host ""
|
|
29
|
+
Write-Host "[Handoff Setup] 완료!"
|
|
30
|
+
Write-Host ""
|
|
31
|
+
Write-Host " 임계값 조정 (기본 70%):"
|
|
32
|
+
Write-Host " [System.Environment]::SetEnvironmentVariable('CONTEXT_THRESHOLD','65','User')"
|
|
33
|
+
Write-Host ""
|
|
34
|
+
Write-Host " 사용법:"
|
|
35
|
+
Write-Host " /handoff — 현재 세션 저장"
|
|
36
|
+
Write-Host " /resume — 이전 세션 복원"
|
|
37
|
+
Write-Host ""
|
|
38
|
+
Write-Host " SessionStart 훅이 세션 시작 시 자동으로 이전 컨텍스트를 복원합니다."
|
package/scripts/setup.sh
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# context-handoff-mcp-server — one-line setup for Linux/macOS
|
|
3
|
+
# Usage: curl -fsSL https://raw.githubusercontent.com/Ethualo/context-handoff-mcp-server/main/scripts/setup.sh | bash
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PLUGIN_NAME="claude-context-handoff"
|
|
8
|
+
HOOKS_TARGET="$HOME/.claude/hooks.json"
|
|
9
|
+
|
|
10
|
+
echo "[Handoff Setup] Installing $PLUGIN_NAME..."
|
|
11
|
+
|
|
12
|
+
# 1. npm global install (builds TypeScript)
|
|
13
|
+
npm install -g claude-context-auto-handoff
|
|
14
|
+
|
|
15
|
+
# 2. hooks.json 등록 (기존 파일이 있으면 경고만)
|
|
16
|
+
HOOKS_SRC="$(npm root -g)/claude-context-auto-handoff/hooks/hooks.json"
|
|
17
|
+
|
|
18
|
+
if [ -f "$HOOKS_TARGET" ]; then
|
|
19
|
+
echo "[Handoff Setup] WARNING: $HOOKS_TARGET 이미 존재합니다."
|
|
20
|
+
echo " 수동으로 $HOOKS_SRC 내용을 병합하세요."
|
|
21
|
+
else
|
|
22
|
+
mkdir -p "$(dirname "$HOOKS_TARGET")"
|
|
23
|
+
cp "$HOOKS_SRC" "$HOOKS_TARGET"
|
|
24
|
+
echo "[Handoff Setup] hooks.json 등록 완료: $HOOKS_TARGET"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo ""
|
|
28
|
+
echo "[Handoff Setup] 완료!"
|
|
29
|
+
echo ""
|
|
30
|
+
echo " 임계값 조정 (기본 70%):"
|
|
31
|
+
echo " export CONTEXT_THRESHOLD=65 # ~/.bashrc 또는 ~/.zshrc에 추가"
|
|
32
|
+
echo ""
|
|
33
|
+
echo " 사용법:"
|
|
34
|
+
echo " /handoff — 현재 세션 저장"
|
|
35
|
+
echo " /resume — 이전 세션 복원"
|
|
36
|
+
echo ""
|
|
37
|
+
echo " SessionStart 훅이 세션 시작 시 자동으로 이전 컨텍스트를 복원합니다."
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node -e \"const fs=require('fs'),path=require('path');const f=path.join(process.cwd(),'.claude','handoff.md');if(fs.existsSync(f)){console.log('CONTEXT HANDOFF FOUND — restore this context before doing anything else:');console.log('---');console.log(fs.readFileSync(f,'utf8'));console.log('---');console.log('Begin from first uncompleted Next Step. Never retry Failed Approaches.')}\"",
|
|
10
|
+
"timeout": 5,
|
|
11
|
+
"statusMessage": "Restoring session context"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PreCompact": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "*",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node -e \"console.log('CRITICAL: Context is about to be compressed. You MUST call generate_handoff_manifest NOW with a complete session summary before this happens. Include: summary, nextSteps, taskDescription, currentStatus, keyDecisions, failedApproaches, modifiedFiles, implicitRules. Do not skip this step.')\"",
|
|
23
|
+
"timeout": 5,
|
|
24
|
+
"statusMessage": "Saving handoff before compaction"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"Stop": [
|
|
30
|
+
{
|
|
31
|
+
"matcher": "*",
|
|
32
|
+
"hooks": [
|
|
33
|
+
{
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "node -e \"const fs=require('fs'),path=require('path');const f=path.join(process.cwd(),'.claude','handoff.md');if(!fs.existsSync(f)){console.log('No handoff file found. If this session had meaningful work, call generate_handoff_manifest before ending.')}else{const age=(Date.now()-fs.statSync(f).mtimeMs)/1000;if(age>300){console.log('HANDOFF STALE (last saved '+Math.round(age/60)+' min ago). Call generate_handoff_manifest to update session context.')}}\"",
|
|
36
|
+
"timeout": 5,
|
|
37
|
+
"statusMessage": "Checking handoff status"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Context Handoff — Session Instructions
|
|
2
|
+
|
|
3
|
+
Copy this file to your project root as `AGENTS.md` when using the `context-handoff` MCP server with Codex.
|
|
4
|
+
|
|
5
|
+
## Session Start
|
|
6
|
+
|
|
7
|
+
If `.claude/handoff.md` exists in the project root, read it immediately before doing anything else. Restore all context: task description, current status, key decisions, failed approaches, blockers, and next steps. Begin from the first uncompleted next step.
|
|
8
|
+
|
|
9
|
+
If the file does not exist, proceed normally.
|
|
10
|
+
|
|
11
|
+
## Session End / Before Context Grows Large
|
|
12
|
+
|
|
13
|
+
Before ending the session or when the conversation is getting long, call `generate_handoff_manifest` with the following fields:
|
|
14
|
+
|
|
15
|
+
| Field | Required | Format |
|
|
16
|
+
|-------|----------|--------|
|
|
17
|
+
| `summary` | ✅ | Terse session recap. Telegraphese — no articles, no filler. |
|
|
18
|
+
| `nextSteps` | ✅ | Ordered array of next tasks |
|
|
19
|
+
| `taskDescription` | recommended | Goal + why it matters to the project |
|
|
20
|
+
| `currentStatus` | recommended | What is done vs what remains — state WHY, not just what |
|
|
21
|
+
| `keyDecisions` | recommended | `"Decision: X — Reason: Y"` |
|
|
22
|
+
| `failedApproaches` | recommended | `"Approach: X → Result: Y → Lesson: Z"` |
|
|
23
|
+
| `modifiedFiles` | recommended | `"path/to/file: what changed"` — NO code snippets |
|
|
24
|
+
| `implicitRules` | recommended | Tech stack, naming conventions, env vars |
|
|
25
|
+
| `blockers` | optional | Unresolved errors or open questions |
|
|
26
|
+
|
|
27
|
+
Output is saved to `.claude/handoff.md` (latest) and `.claude/handoffs/handoff-{timestamp}.md` (archive).
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- **WHY over WHAT**: Every decision and status must state the reason, not just the action.
|
|
32
|
+
- **No code snippets** in any field — reference file paths and delta notes only.
|
|
33
|
+
- **failedApproaches is mandatory** when any approach was tried and failed — prevents the next session from repeating mistakes.
|