claude-session-continuity-mcp 1.6.4 → 1.6.6
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/dist/hooks/install.js +48 -72
- package/dist/hooks/session-end.js +16 -4
- package/dist/hooks/session-start.js +18 -7
- package/dist/index.js +8 -2
- package/package.json +1 -1
package/dist/hooks/install.js
CHANGED
|
@@ -134,73 +134,40 @@ function install() {
|
|
|
134
134
|
// ===== 1. Hooks 설치 (npm exec 방식 - 경로 독립적) =====
|
|
135
135
|
console.log('📌 Step 1: Installing Hooks (npm exec mode)...');
|
|
136
136
|
const settings = loadSettings();
|
|
137
|
-
// 기존 hooks 유지하면서
|
|
137
|
+
// 기존 hooks 유지하면서 우리 훅만 추가/교체
|
|
138
138
|
const hooks = settings.hooks || {};
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
matcher: 'Write',
|
|
174
|
-
hooks: [
|
|
175
|
-
{
|
|
176
|
-
type: 'command',
|
|
177
|
-
command: 'npm exec -- claude-hook-post-tool'
|
|
178
|
-
}
|
|
179
|
-
]
|
|
180
|
-
}
|
|
181
|
-
];
|
|
182
|
-
// PreCompact Hook - 컨텍스트 압축 전 중요 정보 저장
|
|
183
|
-
hooks.PreCompact = [
|
|
184
|
-
{
|
|
185
|
-
hooks: [
|
|
186
|
-
{
|
|
187
|
-
type: 'command',
|
|
188
|
-
command: 'npm exec -- claude-hook-pre-compact'
|
|
189
|
-
}
|
|
190
|
-
]
|
|
191
|
-
}
|
|
192
|
-
];
|
|
193
|
-
// Stop Hook - 세션 종료 시 자동 저장
|
|
194
|
-
hooks.Stop = [
|
|
195
|
-
{
|
|
196
|
-
hooks: [
|
|
197
|
-
{
|
|
198
|
-
type: 'command',
|
|
199
|
-
command: 'npm exec -- claude-hook-session-end'
|
|
200
|
-
}
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
];
|
|
139
|
+
// 우리 훅 명령어 prefix (이걸로 우리 훅인지 판별)
|
|
140
|
+
const OUR_PREFIX = 'claude-hook-';
|
|
141
|
+
/**
|
|
142
|
+
* 기존 훅 배열에서 우리 훅만 제거하고, 새 훅을 추가
|
|
143
|
+
* 사용자 커스텀 훅은 보존됨
|
|
144
|
+
*/
|
|
145
|
+
function mergeHooks(event, ourEntries) {
|
|
146
|
+
const existing = (hooks[event] || []);
|
|
147
|
+
// 기존 항목 중 우리 훅이 아닌 것만 보존
|
|
148
|
+
const userEntries = existing.filter(entry => {
|
|
149
|
+
const cmds = entry.hooks || [];
|
|
150
|
+
return !cmds.some(h => h.command && h.command.includes(OUR_PREFIX));
|
|
151
|
+
});
|
|
152
|
+
// 사용자 훅 먼저, 우리 훅 뒤에 추가
|
|
153
|
+
hooks[event] = [...userEntries, ...ourEntries];
|
|
154
|
+
}
|
|
155
|
+
mergeHooks('SessionStart', [
|
|
156
|
+
{ hooks: [{ type: 'command', command: 'npm exec -- claude-hook-session-start' }] }
|
|
157
|
+
]);
|
|
158
|
+
mergeHooks('UserPromptSubmit', [
|
|
159
|
+
{ hooks: [{ type: 'command', command: 'npm exec -- claude-hook-user-prompt' }] }
|
|
160
|
+
]);
|
|
161
|
+
mergeHooks('PostToolUse', [
|
|
162
|
+
{ matcher: 'Edit', hooks: [{ type: 'command', command: 'npm exec -- claude-hook-post-tool' }] },
|
|
163
|
+
{ matcher: 'Write', hooks: [{ type: 'command', command: 'npm exec -- claude-hook-post-tool' }] }
|
|
164
|
+
]);
|
|
165
|
+
mergeHooks('PreCompact', [
|
|
166
|
+
{ hooks: [{ type: 'command', command: 'npm exec -- claude-hook-pre-compact' }] }
|
|
167
|
+
]);
|
|
168
|
+
mergeHooks('Stop', [
|
|
169
|
+
{ hooks: [{ type: 'command', command: 'npm exec -- claude-hook-session-end' }] }
|
|
170
|
+
]);
|
|
204
171
|
settings.hooks = hooks;
|
|
205
172
|
saveSettings(settings);
|
|
206
173
|
console.log('✅ Hooks installed (npm exec mode - works with local or global install!)');
|
|
@@ -235,12 +202,21 @@ function uninstall() {
|
|
|
235
202
|
console.log('🔧 Removing Claude Code Hooks...');
|
|
236
203
|
const settings = loadSettings();
|
|
237
204
|
const hooks = settings.hooks || {};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
205
|
+
const OUR_PREFIX = 'claude-hook-';
|
|
206
|
+
// 각 이벤트에서 우리 훅만 제거, 사용자 훅은 보존
|
|
207
|
+
for (const event of ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'PreCompact', 'Stop']) {
|
|
208
|
+
const existing = (hooks[event] || []);
|
|
209
|
+
const remaining = existing.filter(entry => {
|
|
210
|
+
const cmds = entry.hooks || [];
|
|
211
|
+
return !cmds.some(h => h.command && h.command.includes(OUR_PREFIX));
|
|
212
|
+
});
|
|
213
|
+
if (remaining.length === 0) {
|
|
214
|
+
delete hooks[event];
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
hooks[event] = remaining;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
244
220
|
if (Object.keys(hooks).length === 0) {
|
|
245
221
|
delete settings.hooks;
|
|
246
222
|
}
|
|
@@ -6,10 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
-
import * as os from 'os';
|
|
10
9
|
import Database from 'better-sqlite3';
|
|
11
|
-
function
|
|
12
|
-
|
|
10
|
+
function detectWorkspaceRoot(cwd) {
|
|
11
|
+
let current = cwd;
|
|
12
|
+
const root = path.parse(current).root;
|
|
13
|
+
while (current !== root) {
|
|
14
|
+
if (fs.existsSync(path.join(current, 'apps')))
|
|
15
|
+
return current;
|
|
16
|
+
if (fs.existsSync(path.join(current, '.claude', 'sessions.db')))
|
|
17
|
+
return current;
|
|
18
|
+
current = path.dirname(current);
|
|
19
|
+
}
|
|
20
|
+
return cwd;
|
|
21
|
+
}
|
|
22
|
+
function getDbPath(cwd) {
|
|
23
|
+
const workspaceRoot = detectWorkspaceRoot(cwd);
|
|
24
|
+
const claudeDir = path.join(workspaceRoot, '.claude');
|
|
13
25
|
if (!fs.existsSync(claudeDir)) {
|
|
14
26
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
15
27
|
}
|
|
@@ -100,7 +112,7 @@ async function main() {
|
|
|
100
112
|
const input = inputData ? JSON.parse(inputData) : {};
|
|
101
113
|
const cwd = input.cwd || process.cwd();
|
|
102
114
|
const project = detectProject(cwd);
|
|
103
|
-
const dbPath = getDbPath();
|
|
115
|
+
const dbPath = getDbPath(cwd);
|
|
104
116
|
if (!fs.existsSync(dbPath)) {
|
|
105
117
|
console.log('[SessionEnd] No DB found, skipping');
|
|
106
118
|
process.exit(0);
|
|
@@ -13,10 +13,6 @@ function detectWorkspaceRoot(cwd) {
|
|
|
13
13
|
return current;
|
|
14
14
|
if (fs.existsSync(path.join(current, '.claude', 'sessions.db')))
|
|
15
15
|
return current;
|
|
16
|
-
if (fs.existsSync(path.join(current, 'package.json'))) {
|
|
17
|
-
// package.json이 있으면 여기가 프로젝트 루트일 가능성 높음
|
|
18
|
-
return current;
|
|
19
|
-
}
|
|
20
16
|
current = path.dirname(current);
|
|
21
17
|
}
|
|
22
18
|
return cwd;
|
|
@@ -28,9 +24,24 @@ function getProject(cwd, workspaceRoot) {
|
|
|
28
24
|
const relative = path.relative(appsDir, cwd);
|
|
29
25
|
return relative.split(path.sep)[0];
|
|
30
26
|
}
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
33
|
-
return
|
|
27
|
+
// 워크스페이스 루트 자체에서 실행
|
|
28
|
+
if (cwd === workspaceRoot) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
// apps/ 외부 하위 프로젝트 (hackathons/ 등) - package.json에서 이름 추출
|
|
32
|
+
let current = cwd;
|
|
33
|
+
while (current !== workspaceRoot && current !== path.parse(current).root) {
|
|
34
|
+
const pkgPath = path.join(current, 'package.json');
|
|
35
|
+
if (fs.existsSync(pkgPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
38
|
+
return pkg.name || path.basename(current);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return path.basename(current);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
current = path.dirname(current);
|
|
34
45
|
}
|
|
35
46
|
return null;
|
|
36
47
|
}
|
package/dist/index.js
CHANGED
|
@@ -692,7 +692,9 @@ async function handleTool(name, args) {
|
|
|
692
692
|
const projectPath = getProjectPath(project);
|
|
693
693
|
if (!await fileExists(projectPath)) {
|
|
694
694
|
// DB에 컨텍스트가 있는지 확인 (디렉토리 없어도 컨텍스트는 있을 수 있음)
|
|
695
|
-
const hasContext = db.prepare('SELECT 1 FROM project_context WHERE project = ?').get(project)
|
|
695
|
+
const hasContext = db.prepare('SELECT 1 FROM project_context WHERE project = ?').get(project)
|
|
696
|
+
|| db.prepare('SELECT 1 FROM active_context WHERE project = ?').get(project)
|
|
697
|
+
|| db.prepare('SELECT 1 FROM sessions WHERE project = ? LIMIT 1').get(project);
|
|
696
698
|
if (!hasContext) {
|
|
697
699
|
return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
|
|
698
700
|
}
|
|
@@ -847,7 +849,11 @@ async function handleTool(name, args) {
|
|
|
847
849
|
const project = args.project;
|
|
848
850
|
const projectPath = getProjectPath(project);
|
|
849
851
|
if (!await fileExists(projectPath)) {
|
|
850
|
-
|
|
852
|
+
const hasData = db.prepare('SELECT 1 FROM active_context WHERE project = ?').get(project)
|
|
853
|
+
|| db.prepare('SELECT 1 FROM sessions WHERE project = ? LIMIT 1').get(project);
|
|
854
|
+
if (!hasData) {
|
|
855
|
+
return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
|
|
856
|
+
}
|
|
851
857
|
}
|
|
852
858
|
// 태스크 통계
|
|
853
859
|
const taskStats = db.prepare(`
|