claude-session-continuity-mcp 1.1.1 → 1.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.
package/README.md CHANGED
@@ -458,4 +458,3 @@ PRs welcome! Please:
458
458
 
459
459
  - [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic
460
460
  - [Xenova Transformers](https://github.com/xenova/transformers.js) for embeddings
461
- - Inspired by [mcp-memory-service](https://github.com/doobidoo/mcp-memory-service)
@@ -1,10 +1,70 @@
1
1
  // SQLite 데이터베이스 초기화 및 관리
2
2
  import Database from 'better-sqlite3';
3
3
  import * as path from 'path';
4
+ import * as fs from 'fs';
5
+ import * as os from 'os';
6
+ // ===== 경로 자동 감지 =====
7
+ /**
8
+ * 워크스페이스 루트 자동 감지
9
+ * 우선순위:
10
+ * 1. WORKSPACE_ROOT 환경변수
11
+ * 2. 현재 디렉토리에서 상위 탐색 (.claude/, apps/, turbo.json)
12
+ * 3. 현재 작업 디렉토리
13
+ * 4. 홈 디렉토리 (fallback)
14
+ */
15
+ function detectWorkspaceRoot() {
16
+ // 1. 환경변수 우선
17
+ if (process.env.WORKSPACE_ROOT) {
18
+ return process.env.WORKSPACE_ROOT;
19
+ }
20
+ // 2. 현재 디렉토리에서 상위로 탐색
21
+ let current = process.cwd();
22
+ const root = path.parse(current).root;
23
+ while (current !== root) {
24
+ // 모노레포 root 감지
25
+ if (fs.existsSync(path.join(current, 'apps'))) {
26
+ return current;
27
+ }
28
+ // .claude 디렉토리가 있으면 여기가 프로젝트 루트
29
+ if (fs.existsSync(path.join(current, '.claude'))) {
30
+ return current;
31
+ }
32
+ // turbo.json + package.json = 모노레포
33
+ if (fs.existsSync(path.join(current, 'turbo.json')) && fs.existsSync(path.join(current, 'package.json'))) {
34
+ return current;
35
+ }
36
+ current = path.dirname(current);
37
+ }
38
+ // 3. 현재 작업 디렉토리 사용 (단일 프로젝트로 간주)
39
+ return process.cwd();
40
+ }
41
+ /**
42
+ * DB 경로 결정
43
+ * - 워크스페이스 루트의 .claude/sessions.db
44
+ * - 없으면 생성
45
+ */
46
+ function getDbPath(workspaceRoot) {
47
+ const claudeDir = path.join(workspaceRoot, '.claude');
48
+ // .claude 디렉토리 생성
49
+ if (!fs.existsSync(claudeDir)) {
50
+ try {
51
+ fs.mkdirSync(claudeDir, { recursive: true });
52
+ }
53
+ catch {
54
+ // 권한 없으면 홈 디렉토리에 생성
55
+ const homeClaudeDir = path.join(os.homedir(), '.claude');
56
+ if (!fs.existsSync(homeClaudeDir)) {
57
+ fs.mkdirSync(homeClaudeDir, { recursive: true });
58
+ }
59
+ return path.join(homeClaudeDir, 'sessions.db');
60
+ }
61
+ }
62
+ return path.join(claudeDir, 'sessions.db');
63
+ }
4
64
  // 기본 경로 설정
5
- export const WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || '/Users/ibyeongchang/Documents/dev/ai-service-generator';
65
+ export const WORKSPACE_ROOT = detectWorkspaceRoot();
6
66
  export const APPS_DIR = path.join(WORKSPACE_ROOT, 'apps');
7
- const DB_PATH = path.join(WORKSPACE_ROOT, '.claude', 'sessions.db');
67
+ const DB_PATH = getDbPath(WORKSPACE_ROOT);
8
68
  // 데이터베이스 인스턴스
9
69
  export const db = new Database(DB_PATH);
10
70
  // Content Filtering 패턴 캐시
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code Hooks + MCP Server 자동 설치 스크립트
4
+ *
5
+ * npm install 시 자동으로:
6
+ * 1. ~/.claude/settings.local.json에 Hook 등록
7
+ * 2. ~/.claude.json에 MCP 서버 등록
8
+ */
9
+ export {};
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code Hooks + MCP Server 자동 설치 스크립트
4
+ *
5
+ * npm install 시 자동으로:
6
+ * 1. ~/.claude/settings.local.json에 Hook 등록
7
+ * 2. ~/.claude.json에 MCP 서버 등록
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import * as os from 'os';
12
+ import { fileURLToPath } from 'url';
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
16
+ const SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.local.json');
17
+ const MCP_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
18
+ // 설치된 패키지 경로 찾기
19
+ function getPackagePath() {
20
+ // 1. 글로벌 설치 확인
21
+ const globalPath = path.dirname(process.argv[1]);
22
+ if (fs.existsSync(path.join(globalPath, 'hooks'))) {
23
+ return globalPath;
24
+ }
25
+ // 2. 로컬 node_modules 확인
26
+ let current = process.cwd();
27
+ while (current !== path.parse(current).root) {
28
+ const candidate = path.join(current, 'node_modules', 'claude-session-continuity-mcp', 'dist', 'hooks');
29
+ if (fs.existsSync(candidate)) {
30
+ return path.join(current, 'node_modules', 'claude-session-continuity-mcp', 'dist');
31
+ }
32
+ current = path.dirname(current);
33
+ }
34
+ // 3. 현재 패키지 디렉토리 (ESM 호환)
35
+ return path.dirname(__dirname);
36
+ }
37
+ function loadSettings() {
38
+ if (!fs.existsSync(SETTINGS_FILE)) {
39
+ return {};
40
+ }
41
+ try {
42
+ return JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf-8'));
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ function saveSettings(settings) {
49
+ if (!fs.existsSync(CLAUDE_DIR)) {
50
+ fs.mkdirSync(CLAUDE_DIR, { recursive: true });
51
+ }
52
+ fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
53
+ }
54
+ function loadMcpConfig() {
55
+ if (!fs.existsSync(MCP_CONFIG_FILE)) {
56
+ return {};
57
+ }
58
+ try {
59
+ return JSON.parse(fs.readFileSync(MCP_CONFIG_FILE, 'utf-8'));
60
+ }
61
+ catch {
62
+ return {};
63
+ }
64
+ }
65
+ function saveMcpConfig(config) {
66
+ fs.writeFileSync(MCP_CONFIG_FILE, JSON.stringify(config, null, 2));
67
+ }
68
+ function installMcpServer() {
69
+ console.log('🔧 Registering MCP server...');
70
+ try {
71
+ const config = loadMcpConfig();
72
+ const mcpServers = config.mcpServers || {};
73
+ // 이미 등록되어 있으면 스킵
74
+ if (mcpServers['project-manager']) {
75
+ console.log(' MCP server already registered');
76
+ return true;
77
+ }
78
+ // MCP 서버 등록
79
+ mcpServers['project-manager'] = {
80
+ command: 'npx',
81
+ args: ['claude-session-continuity-mcp']
82
+ };
83
+ config.mcpServers = mcpServers;
84
+ saveMcpConfig(config);
85
+ console.log('✅ MCP server registered in ~/.claude.json');
86
+ return true;
87
+ }
88
+ catch (error) {
89
+ console.error('⚠️ Failed to register MCP server:', error);
90
+ console.log(' You can manually add to ~/.claude.json:');
91
+ console.log(' {');
92
+ console.log(' "mcpServers": {');
93
+ console.log(' "project-manager": {');
94
+ console.log(' "command": "npx",');
95
+ console.log(' "args": ["claude-session-continuity-mcp"]');
96
+ console.log(' }');
97
+ console.log(' }');
98
+ console.log(' }');
99
+ return false;
100
+ }
101
+ }
102
+ function install() {
103
+ console.log('');
104
+ console.log('╔════════════════════════════════════════════════════════════╗');
105
+ console.log('║ Claude Session Continuity MCP - Installation ║');
106
+ console.log('╚════════════════════════════════════════════════════════════╝');
107
+ console.log('');
108
+ const packagePath = getPackagePath();
109
+ const hooksDir = path.join(packagePath, 'hooks');
110
+ // Hook 스크립트 경로
111
+ const sessionStartHook = path.join(hooksDir, 'session-start.js');
112
+ const userPromptHook = path.join(hooksDir, 'user-prompt-submit.js');
113
+ // ===== 1. Hooks 설치 =====
114
+ console.log('📌 Step 1: Installing Hooks...');
115
+ const settings = loadSettings();
116
+ // 기존 hooks 유지하면서 추가
117
+ const hooks = settings.hooks || {};
118
+ // SessionStart Hook
119
+ hooks.SessionStart = [
120
+ {
121
+ hooks: [
122
+ {
123
+ type: 'command',
124
+ command: `node "${sessionStartHook}"`
125
+ }
126
+ ]
127
+ }
128
+ ];
129
+ // UserPromptSubmit Hook
130
+ hooks.UserPromptSubmit = [
131
+ {
132
+ hooks: [
133
+ {
134
+ type: 'command',
135
+ command: `node "${userPromptHook}"`
136
+ }
137
+ ]
138
+ }
139
+ ];
140
+ settings.hooks = hooks;
141
+ saveSettings(settings);
142
+ console.log('✅ Hooks installed');
143
+ console.log(` SessionStart: ${sessionStartHook}`);
144
+ console.log(` UserPromptSubmit: ${userPromptHook}`);
145
+ console.log('');
146
+ // ===== 2. MCP 서버 등록 =====
147
+ console.log('📌 Step 2: Registering MCP Server...');
148
+ installMcpServer();
149
+ console.log('');
150
+ // ===== 완료 메시지 =====
151
+ console.log('╔════════════════════════════════════════════════════════════╗');
152
+ console.log('║ ✅ Installation Complete! ║');
153
+ console.log('╠════════════════════════════════════════════════════════════╣');
154
+ console.log('║ ║');
155
+ console.log('║ 🚀 Restart Claude Code to activate: ║');
156
+ console.log('║ - 24 MCP tools (session_start, memory_store, etc.) ║');
157
+ console.log('║ - Auto context injection on session start ║');
158
+ console.log('║ ║');
159
+ console.log('║ 📖 Quick Start: ║');
160
+ console.log('║ 1. Start a new Claude Code session ║');
161
+ console.log('║ 2. Context will be auto-injected ║');
162
+ console.log('║ 3. Use session_end to save context ║');
163
+ console.log('║ ║');
164
+ console.log('╚════════════════════════════════════════════════════════════╝');
165
+ console.log('');
166
+ }
167
+ function uninstall() {
168
+ console.log('🔧 Removing Claude Code Hooks...');
169
+ const settings = loadSettings();
170
+ const hooks = settings.hooks || {};
171
+ // session-continuity 관련 Hook만 제거
172
+ delete hooks.SessionStart;
173
+ delete hooks.UserPromptSubmit;
174
+ if (Object.keys(hooks).length === 0) {
175
+ delete settings.hooks;
176
+ }
177
+ else {
178
+ settings.hooks = hooks;
179
+ }
180
+ saveSettings(settings);
181
+ console.log('✅ Hooks removed successfully!');
182
+ }
183
+ function status() {
184
+ console.log('📊 Claude Code Hooks Status\n');
185
+ if (!fs.existsSync(SETTINGS_FILE)) {
186
+ console.log('❌ No hooks configured');
187
+ return;
188
+ }
189
+ const settings = loadSettings();
190
+ const hooks = settings.hooks;
191
+ if (!hooks) {
192
+ console.log('❌ No hooks configured');
193
+ return;
194
+ }
195
+ console.log('Configured hooks:');
196
+ for (const [event, hookList] of Object.entries(hooks)) {
197
+ console.log(` ${event}:`);
198
+ for (const hook of hookList) {
199
+ for (const h of hook.hooks || []) {
200
+ console.log(` → ${h.command}`);
201
+ }
202
+ }
203
+ }
204
+ }
205
+ // CLI
206
+ const args = process.argv.slice(2);
207
+ const command = args[0] || 'install';
208
+ switch (command) {
209
+ case 'install':
210
+ install();
211
+ break;
212
+ case 'uninstall':
213
+ case 'remove':
214
+ uninstall();
215
+ break;
216
+ case 'status':
217
+ status();
218
+ break;
219
+ default:
220
+ console.log('Usage: npx claude-session-continuity-hooks [install|uninstall|status]');
221
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart Hook - 세션 시작 시 컨텍스트 자동 주입
4
+ */
5
+ export {};
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SessionStart Hook - 세션 시작 시 컨텍스트 자동 주입
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import Database from 'better-sqlite3';
8
+ function detectWorkspaceRoot(cwd) {
9
+ let current = cwd;
10
+ const root = path.parse(current).root;
11
+ while (current !== root) {
12
+ if (fs.existsSync(path.join(current, 'apps')))
13
+ return current;
14
+ if (fs.existsSync(path.join(current, '.claude', 'sessions.db')))
15
+ return current;
16
+ if (fs.existsSync(path.join(current, 'package.json'))) {
17
+ // package.json이 있으면 여기가 프로젝트 루트일 가능성 높음
18
+ return current;
19
+ }
20
+ current = path.dirname(current);
21
+ }
22
+ return cwd;
23
+ }
24
+ function getProject(cwd, workspaceRoot) {
25
+ const appsDir = path.join(workspaceRoot, 'apps');
26
+ // apps/ 하위인지 확인
27
+ if (cwd.startsWith(appsDir + path.sep)) {
28
+ const relative = path.relative(appsDir, cwd);
29
+ return relative.split(path.sep)[0];
30
+ }
31
+ // 단일 프로젝트 모드
32
+ if (!fs.existsSync(appsDir)) {
33
+ return path.basename(workspaceRoot);
34
+ }
35
+ return null;
36
+ }
37
+ function loadContext(dbPath, project) {
38
+ if (!fs.existsSync(dbPath))
39
+ return null;
40
+ try {
41
+ const db = new Database(dbPath, { readonly: true });
42
+ const lines = [`# ${project} - Session Resumed\n`];
43
+ // 기술 스택
44
+ const fixed = db.prepare('SELECT tech_stack FROM project_context WHERE project = ?').get(project);
45
+ if (fixed?.tech_stack) {
46
+ const stack = JSON.parse(fixed.tech_stack);
47
+ const stackStr = Object.entries(stack).map(([k, v]) => `**${k}**: ${v}`).join(', ');
48
+ lines.push(`## Tech Stack\n${stackStr}\n`);
49
+ }
50
+ // 현재 상태
51
+ const active = db.prepare('SELECT current_state, blockers FROM active_context WHERE project = ?').get(project);
52
+ if (active?.current_state) {
53
+ lines.push(`## Current State\n📍 ${active.current_state}`);
54
+ if (active.blockers)
55
+ lines.push(`🚧 **Blocker**: ${active.blockers}`);
56
+ lines.push('');
57
+ }
58
+ // 마지막 세션
59
+ const last = db.prepare('SELECT last_work, next_tasks, timestamp FROM sessions WHERE project = ? ORDER BY timestamp DESC LIMIT 1').get(project);
60
+ if (last?.last_work) {
61
+ lines.push(`## Last Session (${last.timestamp?.slice(0, 10) || 'unknown'})`);
62
+ lines.push(`**Work**: ${last.last_work}`);
63
+ if (last.next_tasks) {
64
+ const next = JSON.parse(last.next_tasks);
65
+ if (next.length > 0)
66
+ lines.push(`**Next**: ${next.slice(0, 3).join(' → ')}`);
67
+ }
68
+ lines.push('');
69
+ }
70
+ // 미완료 태스크
71
+ const tasks = db.prepare(`
72
+ SELECT title, priority, status FROM tasks
73
+ WHERE project = ? AND status IN ('pending', 'in_progress')
74
+ ORDER BY priority DESC LIMIT 5
75
+ `).all(project);
76
+ if (tasks.length > 0) {
77
+ lines.push('## 📋 Pending Tasks');
78
+ for (const t of tasks) {
79
+ const icon = t.status === 'in_progress' ? '🔄' : '⏳';
80
+ lines.push(`- ${icon} [P${t.priority}] ${t.title}`);
81
+ }
82
+ lines.push('');
83
+ }
84
+ // 중요 메모리
85
+ const memories = db.prepare(`
86
+ SELECT content, memory_type FROM memories
87
+ WHERE project = ?
88
+ ORDER BY importance DESC, created_at DESC LIMIT 5
89
+ `).all(project);
90
+ if (memories.length > 0) {
91
+ const typeIcons = {
92
+ observation: '👀', decision: '🎯', learning: '📚', error: '⚠️', pattern: '🔄'
93
+ };
94
+ lines.push('## 🧠 Key Memories');
95
+ for (const m of memories) {
96
+ const icon = typeIcons[m.memory_type] || '💭';
97
+ const content = m.content.length > 100 ? m.content.slice(0, 100) + '...' : m.content;
98
+ lines.push(`- ${icon} [${m.memory_type}] ${content}`);
99
+ }
100
+ lines.push('');
101
+ }
102
+ db.close();
103
+ lines.push('---');
104
+ lines.push('_Auto-injected by session-continuity. Use `session_end` when done._');
105
+ return lines.join('\n');
106
+ }
107
+ catch (e) {
108
+ return null;
109
+ }
110
+ }
111
+ async function main() {
112
+ try {
113
+ // stdin에서 입력 읽기
114
+ let inputData = '';
115
+ for await (const chunk of process.stdin) {
116
+ inputData += chunk;
117
+ }
118
+ const input = inputData ? JSON.parse(inputData) : {};
119
+ const cwd = input.cwd || process.cwd();
120
+ const workspaceRoot = detectWorkspaceRoot(cwd);
121
+ const project = getProject(cwd, workspaceRoot);
122
+ if (!project) {
123
+ process.exit(0);
124
+ }
125
+ const dbPath = path.join(workspaceRoot, '.claude', 'sessions.db');
126
+ const context = loadContext(dbPath, project);
127
+ if (context) {
128
+ console.log(`\n<session-context project="${project}">\n${context}\n</session-context>\n`);
129
+ }
130
+ else {
131
+ console.log(`\n[Session] Project: ${project} (no context yet)\n`);
132
+ }
133
+ process.exit(0);
134
+ }
135
+ catch (e) {
136
+ // 에러 시 조용히 종료
137
+ process.exit(0);
138
+ }
139
+ }
140
+ main();
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * UserPromptSubmit Hook - 매 프롬프트마다 관련 컨텍스트 자동 주입
4
+ */
5
+ export {};
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * UserPromptSubmit Hook - 매 프롬프트마다 관련 컨텍스트 자동 주입
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import Database from 'better-sqlite3';
8
+ function detectWorkspaceRoot(cwd) {
9
+ let current = cwd;
10
+ const root = path.parse(current).root;
11
+ while (current !== root) {
12
+ if (fs.existsSync(path.join(current, 'apps')))
13
+ return current;
14
+ if (fs.existsSync(path.join(current, '.claude', 'sessions.db')))
15
+ return current;
16
+ if (fs.existsSync(path.join(current, 'package.json'))) {
17
+ return current;
18
+ }
19
+ current = path.dirname(current);
20
+ }
21
+ return cwd;
22
+ }
23
+ function getProject(cwd, workspaceRoot) {
24
+ const appsDir = path.join(workspaceRoot, 'apps');
25
+ if (cwd.startsWith(appsDir + path.sep)) {
26
+ const relative = path.relative(appsDir, cwd);
27
+ return relative.split(path.sep)[0];
28
+ }
29
+ if (!fs.existsSync(appsDir)) {
30
+ return path.basename(workspaceRoot);
31
+ }
32
+ return null;
33
+ }
34
+ function loadContext(dbPath, project) {
35
+ if (!fs.existsSync(dbPath))
36
+ return null;
37
+ try {
38
+ const db = new Database(dbPath, { readonly: true });
39
+ const lines = [`# 🚀 ${project} Context\n`];
40
+ // 기술 스택
41
+ const fixed = db.prepare('SELECT tech_stack FROM project_context WHERE project = ?').get(project);
42
+ if (fixed?.tech_stack) {
43
+ const stack = JSON.parse(fixed.tech_stack);
44
+ const stackStr = Object.entries(stack).map(([k, v]) => `**${k}**: ${v}`).join(', ');
45
+ lines.push(`## Tech Stack\n${stackStr}\n`);
46
+ }
47
+ // 현재 상태
48
+ const active = db.prepare('SELECT current_state, blockers, last_verification FROM active_context WHERE project = ?').get(project);
49
+ if (active?.current_state) {
50
+ lines.push(`## Current State`);
51
+ lines.push(`📍 ${active.current_state}`);
52
+ if (active.blockers)
53
+ lines.push(`🚧 **Blocker**: ${active.blockers}`);
54
+ if (active.last_verification) {
55
+ const emoji = active.last_verification.includes('passed') ? '✅' : '❌';
56
+ lines.push(`${emoji} Last verify: ${active.last_verification}`);
57
+ }
58
+ lines.push('');
59
+ }
60
+ // 마지막 세션
61
+ const last = db.prepare('SELECT last_work, next_tasks, timestamp FROM sessions WHERE project = ? ORDER BY timestamp DESC LIMIT 1').get(project);
62
+ if (last?.last_work) {
63
+ lines.push(`## Last Session (${last.timestamp?.slice(0, 10) || 'unknown'})`);
64
+ lines.push(`**Work**: ${last.last_work}`);
65
+ if (last.next_tasks) {
66
+ const next = JSON.parse(last.next_tasks);
67
+ if (next.length > 0)
68
+ lines.push(`**Next**: ${next.slice(0, 3).join(' → ')}`);
69
+ }
70
+ lines.push('');
71
+ }
72
+ // 미완료 태스크
73
+ const tasks = db.prepare(`
74
+ SELECT id, title, priority, status FROM tasks
75
+ WHERE project = ? AND status IN ('pending', 'in_progress')
76
+ ORDER BY priority DESC LIMIT 5
77
+ `).all(project);
78
+ if (tasks.length > 0) {
79
+ lines.push('## 📋 Pending Tasks');
80
+ for (const t of tasks) {
81
+ const icon = t.status === 'in_progress' ? '🔄' : '⏳';
82
+ lines.push(`- ${icon} [P${t.priority}] ${t.title} (#${t.id})`);
83
+ }
84
+ lines.push('');
85
+ }
86
+ // 중요 메모리
87
+ const memories = db.prepare(`
88
+ SELECT content, memory_type, importance FROM memories
89
+ WHERE project = ?
90
+ ORDER BY importance DESC, created_at DESC LIMIT 5
91
+ `).all(project);
92
+ if (memories.length > 0) {
93
+ const typeIcons = {
94
+ observation: '👀', decision: '🎯', learning: '📚', error: '⚠️', pattern: '🔄'
95
+ };
96
+ lines.push('## 🧠 Key Memories');
97
+ for (const m of memories) {
98
+ const icon = typeIcons[m.memory_type] || '💭';
99
+ const content = m.content.length > 100 ? m.content.slice(0, 100) + '...' : m.content;
100
+ lines.push(`- ${icon} [${m.memory_type}] ${content}`);
101
+ }
102
+ lines.push('');
103
+ }
104
+ // 최근 에러 솔루션
105
+ const solutions = db.prepare(`
106
+ SELECT error_signature, solution FROM solutions
107
+ WHERE project = ?
108
+ ORDER BY created_at DESC LIMIT 3
109
+ `).all(project);
110
+ if (solutions.length > 0) {
111
+ lines.push('## 🔧 Recent Error Solutions');
112
+ for (const s of solutions) {
113
+ const sol = s.solution.length > 80 ? s.solution.slice(0, 80) + '...' : s.solution;
114
+ lines.push(`- **${s.error_signature}**: ${sol}`);
115
+ }
116
+ lines.push('');
117
+ }
118
+ db.close();
119
+ lines.push('---');
120
+ lines.push('_Auto-injected by MCP v5. Use `session_end` when done._');
121
+ return lines.join('\n');
122
+ }
123
+ catch (e) {
124
+ return null;
125
+ }
126
+ }
127
+ async function main() {
128
+ // 환경 변수로 비활성화 가능
129
+ if (process.env.MCP_HOOKS_DISABLED === 'true') {
130
+ process.exit(0);
131
+ }
132
+ try {
133
+ // stdin에서 입력 읽기 (타임아웃 방지)
134
+ let inputData = '';
135
+ const timeout = setTimeout(() => {
136
+ // 입력 없으면 그냥 진행
137
+ }, 100);
138
+ process.stdin.setEncoding('utf8');
139
+ process.stdin.on('data', (chunk) => {
140
+ inputData += chunk;
141
+ });
142
+ await new Promise((resolve) => {
143
+ process.stdin.on('end', () => {
144
+ clearTimeout(timeout);
145
+ resolve();
146
+ });
147
+ // 100ms 후 타임아웃
148
+ setTimeout(resolve, 100);
149
+ });
150
+ const cwd = process.cwd();
151
+ const workspaceRoot = detectWorkspaceRoot(cwd);
152
+ const project = getProject(cwd, workspaceRoot);
153
+ if (!project) {
154
+ process.exit(0);
155
+ }
156
+ const dbPath = path.join(workspaceRoot, '.claude', 'sessions.db');
157
+ const context = loadContext(dbPath, project);
158
+ if (context) {
159
+ console.log(`\n<project-context project="${project}">\n${context}\n</project-context>\n`);
160
+ }
161
+ process.exit(0);
162
+ }
163
+ catch (e) {
164
+ process.exit(0);
165
+ }
166
+ }
167
+ main();
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
18
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
19
  import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
20
20
  import * as fs from 'fs/promises';
21
+ import { mkdirSync, existsSync } from 'fs';
21
22
  import * as path from 'path';
22
23
  import { execSync } from 'child_process';
23
24
  import Database from 'better-sqlite3';
@@ -26,15 +27,63 @@ import { pipeline, env } from '@xenova/transformers';
26
27
  // 모델 캐시 설정
27
28
  env.cacheDir = path.join(process.env.HOME || '/tmp', '.cache', 'transformers');
28
29
  env.allowLocalModels = true;
29
- // 기본 경로 설정
30
- const WORKSPACE_ROOT = process.env.WORKSPACE_ROOT || '/Users/ibyeongchang/Documents/dev/ai-service-generator';
30
+ // 기본 경로 설정 (자동 감지)
31
+ function detectWorkspaceRoot() {
32
+ // 1. 환경변수가 설정되어 있으면 사용
33
+ if (process.env.WORKSPACE_ROOT) {
34
+ return process.env.WORKSPACE_ROOT;
35
+ }
36
+ // 2. 현재 디렉토리에서 상위로 탐색하며 apps/ 또는 .claude/ 디렉토리 찾기
37
+ let current = process.cwd();
38
+ const root = path.parse(current).root;
39
+ while (current !== root) {
40
+ // apps/ 디렉토리가 있으면 여기가 workspace root
41
+ if (existsSync(path.join(current, 'apps'))) {
42
+ return current;
43
+ }
44
+ // .claude/ 디렉토리가 있으면 여기가 workspace root
45
+ if (existsSync(path.join(current, '.claude'))) {
46
+ return current;
47
+ }
48
+ // package.json + turbo.json이 있으면 모노레포 루트
49
+ if (existsSync(path.join(current, 'package.json')) && existsSync(path.join(current, 'turbo.json'))) {
50
+ return current;
51
+ }
52
+ current = path.dirname(current);
53
+ }
54
+ // 3. 못 찾으면 현재 디렉토리 사용 (경고 출력)
55
+ console.error('Warning: WORKSPACE_ROOT not set and could not auto-detect. Using current directory.');
56
+ console.error('Set WORKSPACE_ROOT environment variable in your MCP config for best results.');
57
+ return process.cwd();
58
+ }
59
+ const WORKSPACE_ROOT = detectWorkspaceRoot();
31
60
  const APPS_DIR = path.join(WORKSPACE_ROOT, 'apps');
32
- const DB_PATH = path.join(WORKSPACE_ROOT, '.claude', 'sessions.db');
61
+ const CLAUDE_DIR = path.join(WORKSPACE_ROOT, '.claude');
62
+ const DB_PATH = path.join(CLAUDE_DIR, 'sessions.db');
63
+ // 모노레포 vs 단일 프로젝트 모드 감지
64
+ const IS_MONOREPO = existsSync(APPS_DIR);
65
+ const DEFAULT_PROJECT = IS_MONOREPO ? null : path.basename(WORKSPACE_ROOT);
66
+ // ===== 디렉토리 생성 (동기) =====
67
+ if (!existsSync(CLAUDE_DIR)) {
68
+ mkdirSync(CLAUDE_DIR, { recursive: true });
69
+ }
33
70
  // ===== SQLite 데이터베이스 초기화 =====
34
71
  const db = new Database(DB_PATH);
35
- // v4 스키마 - 메모리 분류 체계 + 지식 그래프 강화
72
+ // v5 스키마 - 세션 + 메모리 분류 체계 + 지식 그래프
36
73
  db.exec(`
37
- -- 기존 sessions 테이블 사용 (스키마 변경 없음)
74
+ -- 세션 테이블 (핵심)
75
+ CREATE TABLE IF NOT EXISTS sessions (
76
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
77
+ project TEXT NOT NULL,
78
+ last_work TEXT,
79
+ current_status TEXT,
80
+ next_tasks TEXT,
81
+ modified_files TEXT,
82
+ issues TEXT,
83
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
84
+ );
85
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);
86
+ CREATE INDEX IF NOT EXISTS idx_sessions_timestamp ON sessions(timestamp DESC);
38
87
 
39
88
  -- 프로젝트 컨텍스트 (고정)
40
89
  CREATE TABLE IF NOT EXISTS project_context (
@@ -249,6 +298,22 @@ function runCommand(cmd, cwd) {
249
298
  return { success: false, output: e.stdout || e.stderr || e.message || 'Unknown error' };
250
299
  }
251
300
  }
301
+ // ===== 프로젝트 경로 헬퍼 (모노레포/단일 프로젝트 호환) =====
302
+ function getProjectPath(project) {
303
+ // 단일 프로젝트 모드: 프로젝트명이 workspace 이름과 같으면 루트 반환
304
+ if (!IS_MONOREPO && project === DEFAULT_PROJECT) {
305
+ return WORKSPACE_ROOT;
306
+ }
307
+ // 모노레포 모드: apps/ 하위 경로
308
+ return path.join(APPS_DIR, project);
309
+ }
310
+ function resolveProject(project) {
311
+ // 프로젝트명이 없으면 기본 프로젝트 사용 (단일 프로젝트 모드)
312
+ if (!project && DEFAULT_PROJECT) {
313
+ return DEFAULT_PROJECT;
314
+ }
315
+ return project || 'default';
316
+ }
252
317
  // ===== MCP 서버 =====
253
318
  const server = new Server({ name: 'project-manager-v5', version: '5.0.0' }, {
254
319
  capabilities: {
@@ -619,9 +684,17 @@ async function handleTool(name, args) {
619
684
  case 'session_start': {
620
685
  const project = args.project;
621
686
  const compact = args.compact !== false;
622
- const projectPath = path.join(APPS_DIR, project);
623
- if (!await fileExists(projectPath)) {
624
- return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
687
+ // 모노레포 모드에서만 프로젝트 디렉토리 체크
688
+ // 단일 프로젝트 모드나 DB에 이미 데이터가 있으면 스킵
689
+ if (IS_MONOREPO) {
690
+ const projectPath = getProjectPath(project);
691
+ if (!await fileExists(projectPath)) {
692
+ // DB에 컨텍스트가 있는지 확인 (디렉토리 없어도 컨텍스트는 있을 수 있음)
693
+ const hasContext = db.prepare('SELECT 1 FROM project_context WHERE project = ?').get(project);
694
+ if (!hasContext) {
695
+ return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
696
+ }
697
+ }
625
698
  }
626
699
  // 고정 컨텍스트
627
700
  const fixedRow = db.prepare('SELECT * FROM project_context WHERE project = ?').get(project);
@@ -770,7 +843,7 @@ async function handleTool(name, args) {
770
843
  // ===== 프로젝트 관리 =====
771
844
  case 'project_status': {
772
845
  const project = args.project;
773
- const projectPath = path.join(APPS_DIR, project);
846
+ const projectPath = getProjectPath(project);
774
847
  if (!await fileExists(projectPath)) {
775
848
  return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
776
849
  }
@@ -815,7 +888,7 @@ async function handleTool(name, args) {
815
888
  const project = args.project;
816
889
  const techStack = args.techStack;
817
890
  const description = args.description;
818
- const projectPath = path.join(APPS_DIR, project);
891
+ const projectPath = getProjectPath(project);
819
892
  // 기술 스택 자동 감지
820
893
  const detectedStack = await detectTechStack(projectPath);
821
894
  const finalStack = { ...detectedStack, ...techStack };
@@ -836,7 +909,7 @@ async function handleTool(name, args) {
836
909
  }
837
910
  case 'project_analyze': {
838
911
  const project = args.project;
839
- const projectPath = path.join(APPS_DIR, project);
912
+ const projectPath = getProjectPath(project);
840
913
  if (!await fileExists(projectPath)) {
841
914
  return { content: [{ type: 'text', text: `Project not found: ${project}` }] };
842
915
  }
@@ -867,10 +940,18 @@ async function handleTool(name, args) {
867
940
  }
868
941
  case 'list_projects': {
869
942
  try {
870
- const entries = await fs.readdir(APPS_DIR, { withFileTypes: true });
871
- const projects = entries
872
- .filter(e => e.isDirectory() && !e.name.startsWith('.'))
873
- .map(e => e.name);
943
+ let projects = [];
944
+ // 단일 프로젝트 모드
945
+ if (!IS_MONOREPO && DEFAULT_PROJECT) {
946
+ projects = [DEFAULT_PROJECT];
947
+ }
948
+ else if (IS_MONOREPO) {
949
+ // 모노레포 모드: apps/ 하위 디렉토리
950
+ const entries = await fs.readdir(APPS_DIR, { withFileTypes: true });
951
+ projects = entries
952
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
953
+ .map(e => e.name);
954
+ }
874
955
  // 각 프로젝트 상태 조회
875
956
  const projectsWithStatus = await Promise.all(projects.map(async (p) => {
876
957
  const active = db.prepare('SELECT current_state FROM active_context WHERE project = ?').get(p);
@@ -878,7 +959,8 @@ async function handleTool(name, args) {
878
959
  return {
879
960
  name: p,
880
961
  status: active?.current_state || 'No context',
881
- pendingTasks: taskCount?.count || 0
962
+ pendingTasks: taskCount?.count || 0,
963
+ mode: IS_MONOREPO ? 'monorepo' : 'single'
882
964
  };
883
965
  }));
884
966
  return { content: [{ type: 'text', text: JSON.stringify(projectsWithStatus, null, 2) }] };
@@ -931,12 +1013,12 @@ async function handleTool(name, args) {
931
1013
  const tasks = status === 'all'
932
1014
  ? db.prepare(sql).all(project)
933
1015
  : db.prepare(sql).all(project, status);
934
- return { content: [{ type: 'text', text: JSON.stringify(tasks, null, 2) }] };
1016
+ return { content: [{ type: 'text', text: JSON.stringify({ project, status, count: tasks.length, tasks }, null, 2) }] };
935
1017
  }
936
1018
  case 'task_suggest': {
937
1019
  const project = args.project;
938
1020
  const searchPath = args.path;
939
- const projectPath = path.join(APPS_DIR, project, searchPath || '');
1021
+ const projectPath = path.join(getProjectPath(project), searchPath || '');
940
1022
  // TODO, FIXME 등 검색
941
1023
  try {
942
1024
  const result = runCommand(`grep -rn "TODO\\|FIXME\\|HACK\\|XXX" --include="*.ts" --include="*.tsx" --include="*.dart" --include="*.kt" . | head -20`, projectPath);
@@ -984,12 +1066,13 @@ async function handleTool(name, args) {
984
1066
  INSERT INTO solutions (project, error_signature, error_message, solution, related_files, keywords)
985
1067
  VALUES (?, ?, ?, ?, ?, ?)
986
1068
  `).run(project || null, errorSignature, errorMessage || null, solution, relatedFiles ? JSON.stringify(relatedFiles) : null, keywords);
987
- // 임베딩 저장 (시맨틱 검색용)
988
- const embedding = await generateEmbedding(`${errorSignature} ${errorMessage || ''} ${solution}`);
989
- if (embedding) {
990
- const buffer = Buffer.from(new Float32Array(embedding).buffer);
991
- db.prepare('INSERT OR REPLACE INTO embeddings_v3 (type, ref_id, embedding) VALUES (?, ?, ?)').run('solution', result.lastInsertRowid, buffer);
992
- }
1069
+ // 임베딩 저장 (시맨틱 검색용) - embeddings_v4 사용
1070
+ generateEmbedding(`${errorSignature} ${errorMessage || ''} ${solution}`).then(embedding => {
1071
+ if (embedding) {
1072
+ const buffer = Buffer.from(new Float32Array(embedding).buffer);
1073
+ db.prepare('INSERT OR REPLACE INTO embeddings_v4 (entity_type, entity_id, embedding) VALUES (?, ?, ?)').run('solution', result.lastInsertRowid, buffer);
1074
+ }
1075
+ }).catch(() => { });
993
1076
  return {
994
1077
  content: [{
995
1078
  type: 'text',
@@ -1001,55 +1084,30 @@ async function handleTool(name, args) {
1001
1084
  const query = args.query;
1002
1085
  const project = args.project;
1003
1086
  const limit = args.limit || 3;
1004
- // 키워드 검색 먼저
1087
+ // 키워드 검색 (LIKE 기반, 안정적)
1005
1088
  const keywordResults = db.prepare(`
1006
1089
  SELECT * FROM solutions
1007
- WHERE error_signature LIKE ? OR error_message LIKE ? OR keywords LIKE ?
1090
+ WHERE error_signature LIKE ? OR error_message LIKE ? OR solution LIKE ? OR keywords LIKE ?
1008
1091
  ${project ? 'AND project = ?' : ''}
1009
1092
  ORDER BY created_at DESC LIMIT ?
1010
- `).all(`%${query}%`, `%${query}%`, `%${query}%`, ...(project ? [project, limit] : [limit]));
1011
- if (keywordResults.length > 0) {
1012
- return {
1013
- content: [{
1014
- type: 'text',
1015
- text: JSON.stringify(keywordResults.map(r => ({
1016
- id: r.id,
1017
- errorSignature: r.error_signature,
1018
- solution: r.solution,
1019
- project: r.project,
1020
- created: r.created_at
1021
- })), null, 2)
1022
- }]
1023
- };
1024
- }
1025
- // 시맨틱 검색 폴백
1026
- const queryEmb = await generateEmbedding(query);
1027
- if (!queryEmb) {
1028
- return { content: [{ type: 'text', text: 'No solutions found' }] };
1029
- }
1030
- const allSolutions = db.prepare(`
1031
- SELECT s.*, e.embedding FROM solutions s
1032
- LEFT JOIN embeddings_v3 e ON e.type = 'solution' AND e.ref_id = s.id
1033
- ${project ? 'WHERE s.project = ?' : ''}
1034
- LIMIT 50
1035
- `).all(project ? [project] : []);
1036
- const scored = allSolutions.map(s => {
1037
- if (!s.embedding)
1038
- return { ...s, similarity: 0 };
1039
- const emb = Array.from(new Float32Array(s.embedding.buffer));
1040
- return { ...s, similarity: cosineSimilarity(queryEmb, emb) };
1041
- });
1042
- scored.sort((a, b) => b.similarity - a.similarity);
1043
- const topResults = scored.slice(0, limit);
1093
+ `).all(`%${query}%`, `%${query}%`, `%${query}%`, `%${query}%`, ...(project ? [project, limit] : [limit]));
1094
+ const results = keywordResults.map(r => ({
1095
+ id: r.id,
1096
+ errorSignature: r.error_signature,
1097
+ errorMessage: r.error_message,
1098
+ solution: r.solution,
1099
+ project: r.project,
1100
+ relatedFiles: r.related_files,
1101
+ created: r.created_at
1102
+ }));
1044
1103
  return {
1045
1104
  content: [{
1046
1105
  type: 'text',
1047
- text: JSON.stringify(topResults.map(r => ({
1048
- id: r.id,
1049
- errorSignature: r.error_signature,
1050
- solution: r.solution,
1051
- similarity: Math.round(r.similarity * 100) + '%'
1052
- })), null, 2)
1106
+ text: JSON.stringify({
1107
+ query,
1108
+ found: results.length,
1109
+ results
1110
+ }, null, 2)
1053
1111
  }]
1054
1112
  };
1055
1113
  }
@@ -1081,7 +1139,7 @@ async function handleTool(name, args) {
1081
1139
  // ===== 검증/품질 =====
1082
1140
  case 'verify_build': {
1083
1141
  const project = args.project;
1084
- const projectPath = path.join(APPS_DIR, project);
1142
+ const projectPath = getProjectPath(project);
1085
1143
  const platform = await detectPlatform(projectPath);
1086
1144
  let cmd;
1087
1145
  switch (platform) {
@@ -1113,7 +1171,7 @@ async function handleTool(name, args) {
1113
1171
  case 'verify_test': {
1114
1172
  const project = args.project;
1115
1173
  const testPath = args.testPath;
1116
- const projectPath = path.join(APPS_DIR, project);
1174
+ const projectPath = getProjectPath(project);
1117
1175
  const platform = await detectPlatform(projectPath);
1118
1176
  let cmd;
1119
1177
  switch (platform) {
@@ -1137,7 +1195,7 @@ async function handleTool(name, args) {
1137
1195
  case 'verify_all': {
1138
1196
  const project = args.project;
1139
1197
  const stopOnFail = args.stopOnFail === true;
1140
- const projectPath = path.join(APPS_DIR, project);
1198
+ const projectPath = getProjectPath(project);
1141
1199
  const platform = await detectPlatform(projectPath);
1142
1200
  const results = [];
1143
1201
  // Build
@@ -1187,6 +1245,13 @@ async function handleTool(name, args) {
1187
1245
  const tags = args.tags;
1188
1246
  const importance = args.importance || 5;
1189
1247
  const relatedTo = args.relatedTo;
1248
+ // 필수 파라미터 검증
1249
+ if (!content || content.trim().length === 0) {
1250
+ return { content: [{ type: 'text', text: 'Error: content is required and cannot be empty' }] };
1251
+ }
1252
+ if (!memoryType) {
1253
+ return { content: [{ type: 'text', text: 'Error: type is required' }] };
1254
+ }
1190
1255
  // 메모리 저장
1191
1256
  const result = db.prepare(`
1192
1257
  INSERT INTO memories (content, memory_type, tags, project, importance, metadata)
@@ -1250,43 +1315,35 @@ async function handleTool(name, args) {
1250
1315
  }
1251
1316
  }
1252
1317
  else {
1253
- // FTS5 키워드 검색
1254
- const ftsQuery = query.split(/\s+/).filter(w => w.length > 1).map(w => `"${w}"`).join(' OR ');
1318
+ // LIKE 기반 키워드 검색 (FTS5보다 안정적)
1319
+ // 검색어를 단어로 분리하여 OR 조건으로 검색
1320
+ const words = query.split(/\s+/).filter(w => w.length > 0);
1321
+ const likeConditions = words.map(() => '(content LIKE ? OR tags LIKE ?)').join(' OR ');
1322
+ const likeParams = [];
1323
+ words.forEach(w => {
1324
+ likeParams.push(`%${w}%`, `%${w}%`);
1325
+ });
1255
1326
  let sql = `
1256
- SELECT m.* FROM memories m
1257
- JOIN memories_fts fts ON m.id = fts.rowid
1258
- WHERE memories_fts MATCH ?
1259
- AND m.importance >= ?
1327
+ SELECT * FROM memories
1328
+ WHERE (${likeConditions || 'content LIKE ?'})
1329
+ AND importance >= ?
1260
1330
  `;
1261
- const params = [ftsQuery || `"${query}"`, minImportance];
1331
+ const params = [...(likeConditions ? likeParams : [`%${query}%`]), minImportance];
1262
1332
  if (memoryType && memoryType !== 'all') {
1263
- sql += ` AND m.memory_type = ?`;
1333
+ sql += ` AND memory_type = ?`;
1264
1334
  params.push(memoryType);
1265
1335
  }
1266
1336
  if (project) {
1267
- sql += ` AND m.project = ?`;
1337
+ sql += ` AND project = ?`;
1268
1338
  params.push(project);
1269
1339
  }
1270
1340
  if (tags && tags.length > 0) {
1271
- sql += ` AND (${tags.map(() => 'm.tags LIKE ?').join(' OR ')})`;
1341
+ sql += ` AND (${tags.map(() => 'tags LIKE ?').join(' OR ')})`;
1272
1342
  params.push(...tags.map(t => `%"${t}"%`));
1273
1343
  }
1274
- sql += ` ORDER BY m.importance DESC, m.accessed_at DESC LIMIT ?`;
1344
+ sql += ` ORDER BY importance DESC, accessed_at DESC LIMIT ?`;
1275
1345
  params.push(limit);
1276
- try {
1277
- results = db.prepare(sql).all(...params);
1278
- }
1279
- catch {
1280
- // FTS 실패 시 LIKE 폴백
1281
- results = db.prepare(`
1282
- SELECT * FROM memories
1283
- WHERE (content LIKE ? OR tags LIKE ?)
1284
- AND importance >= ?
1285
- ${memoryType && memoryType !== 'all' ? 'AND memory_type = ?' : ''}
1286
- ${project ? 'AND project = ?' : ''}
1287
- ORDER BY importance DESC LIMIT ?
1288
- `).all(`%${query}%`, `%${query}%`, minImportance, ...(memoryType && memoryType !== 'all' ? [memoryType] : []), ...(project ? [project] : []), limit);
1289
- }
1346
+ results = db.prepare(sql).all(...params);
1290
1347
  }
1291
1348
  // 접근 기록 업데이트
1292
1349
  const ids = results.map(r => r.id);
@@ -1623,7 +1680,7 @@ const prompts = [
1623
1680
  ];
1624
1681
  // ===== Prompt 내용 생성 함수 =====
1625
1682
  async function generateProjectContext(project) {
1626
- const projectPath = path.join(APPS_DIR, project);
1683
+ const projectPath = getProjectPath(project);
1627
1684
  // 프로젝트 존재 확인
1628
1685
  if (!await fileExists(projectPath)) {
1629
1686
  return `⚠️ 프로젝트를 찾을 수 없습니다: ${project}\n\napps/ 디렉토리에서 사용 가능한 프로젝트를 확인하세요.`;
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Session Continuity for Claude Code - Never re-explain your project again",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "claude-session-continuity-mcp": "./dist/index.js"
8
+ "claude-session-continuity-mcp": "./dist/index.js",
9
+ "claude-session-hooks": "./dist/hooks/install.js"
9
10
  },
10
11
  "scripts": {
11
12
  "build": "tsc",
@@ -17,6 +18,7 @@
17
18
  "test:coverage": "vitest run --coverage",
18
19
  "dashboard": "node dist/dashboard.js",
19
20
  "dashboard:v2": "node dist/dashboard-v2.js",
21
+ "postinstall": "node dist/hooks/install.js install 2>/dev/null || true",
20
22
  "prepublishOnly": "npm run build && npm test"
21
23
  },
22
24
  "keywords": [
@@ -43,6 +45,7 @@
43
45
  "homepage": "https://github.com/leesgit/claude-session-continuity-mcp#readme",
44
46
  "files": [
45
47
  "dist",
48
+ "dist/hooks",
46
49
  "README.md",
47
50
  "LICENSE"
48
51
  ],