claude-session-continuity-mcp 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/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/dashboard-v2.d.ts +11 -0
- package/dist/dashboard-v2.js +1321 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.js +1196 -0
- package/dist/db/database.d.ts +8 -0
- package/dist/db/database.js +208 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3426 -0
- package/dist/schemas.d.ts +381 -0
- package/dist/schemas.js +119 -0
- package/dist/tools/context.d.ts +6 -0
- package/dist/tools/context.js +227 -0
- package/dist/tools/embedding.d.ts +5 -0
- package/dist/tools/embedding.js +191 -0
- package/dist/tools/feedback.d.ts +5 -0
- package/dist/tools/feedback.js +200 -0
- package/dist/tools/filter.d.ts +5 -0
- package/dist/tools/filter.js +169 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.js +38 -0
- package/dist/tools/learning.d.ts +8 -0
- package/dist/tools/learning.js +395 -0
- package/dist/tools/memory.d.ts +8 -0
- package/dist/tools/memory.js +356 -0
- package/dist/tools/project.d.ts +9 -0
- package/dist/tools/project.js +396 -0
- package/dist/tools/relation.d.ts +4 -0
- package/dist/tools/relation.js +148 -0
- package/dist/tools/session.d.ts +7 -0
- package/dist/tools/session.js +272 -0
- package/dist/tools/solution.d.ts +5 -0
- package/dist/tools/solution.js +182 -0
- package/dist/tools/task.d.ts +6 -0
- package/dist/tools/task.js +184 -0
- package/dist/tools-v2/auto-capture.d.ts +5 -0
- package/dist/tools-v2/auto-capture.js +252 -0
- package/dist/tools-v2/context.d.ts +4 -0
- package/dist/tools-v2/context.js +170 -0
- package/dist/tools-v2/embedding.d.ts +3 -0
- package/dist/tools-v2/embedding.js +115 -0
- package/dist/tools-v2/index.d.ts +13 -0
- package/dist/tools-v2/index.js +73 -0
- package/dist/tools-v2/learn.d.ts +4 -0
- package/dist/tools-v2/learn.js +233 -0
- package/dist/tools-v2/memory.d.ts +6 -0
- package/dist/tools-v2/memory.js +340 -0
- package/dist/tools-v2/projects.d.ts +3 -0
- package/dist/tools-v2/projects.js +218 -0
- package/dist/tools-v2/task.d.ts +3 -0
- package/dist/tools-v2/task.js +193 -0
- package/dist/tools-v2/verify.d.ts +3 -0
- package/dist/tools-v2/verify.js +164 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +7 -0
- package/dist/utils/auto-context.d.ts +58 -0
- package/dist/utils/auto-context.js +234 -0
- package/dist/utils/cache.d.ts +60 -0
- package/dist/utils/cache.js +161 -0
- package/dist/utils/embedding.d.ts +7 -0
- package/dist/utils/embedding.js +67 -0
- package/dist/utils/helpers.d.ts +4 -0
- package/dist/utils/helpers.js +45 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +111 -0
- package/package.json +64 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// 자동 컨텍스트 캡처 도구
|
|
2
|
+
// session_start, session_end 도구로 세션 라이프사이클 관리
|
|
3
|
+
import { db } from '../db/database.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
import { loadContext, saveContext, createContextSnapshot, getCompactContext } from '../utils/auto-context.js';
|
|
6
|
+
import { invalidateProjects } from '../utils/cache.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
// ===== 스키마 =====
|
|
9
|
+
const SessionStartSchema = z.object({
|
|
10
|
+
project: z.string().min(1, 'project is required'),
|
|
11
|
+
compact: z.boolean().optional().default(false)
|
|
12
|
+
});
|
|
13
|
+
const SessionEndSchema = z.object({
|
|
14
|
+
project: z.string().min(1, 'project is required'),
|
|
15
|
+
currentState: z.string().min(1, 'currentState is required'),
|
|
16
|
+
recentFiles: z.array(z.string()).optional(),
|
|
17
|
+
blockers: z.string().optional(),
|
|
18
|
+
verification: z.enum(['passed', 'failed']).optional(),
|
|
19
|
+
architectureDecision: z.string().optional(),
|
|
20
|
+
codePattern: z.string().optional(),
|
|
21
|
+
techStack: z.record(z.string()).optional()
|
|
22
|
+
});
|
|
23
|
+
const SessionSummarySchema = z.object({
|
|
24
|
+
project: z.string().min(1, 'project is required')
|
|
25
|
+
});
|
|
26
|
+
// ===== 도구 정의 =====
|
|
27
|
+
export const autoCaptureTools = [
|
|
28
|
+
{
|
|
29
|
+
name: 'session_start',
|
|
30
|
+
description: `세션 시작 시 자동 컨텍스트 로드. 새 세션의 첫 도구 호출.
|
|
31
|
+
- 프로젝트 컨텍스트 자동 복원 (< 5ms 목표)
|
|
32
|
+
- compact=true: 토큰 효율적 요약 (~650토큰)
|
|
33
|
+
- compact=false: 전체 JSON 컨텍스트
|
|
34
|
+
세션 시작 시 반드시 호출하여 연속성 확보.`,
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
project: { type: 'string', description: '프로젝트명' },
|
|
39
|
+
compact: { type: 'boolean', description: '간결한 요약 모드 (기본: false)', default: false }
|
|
40
|
+
},
|
|
41
|
+
required: ['project']
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'session_end',
|
|
46
|
+
description: `세션 종료 시 자동 컨텍스트 저장. 작업 완료 후 호출.
|
|
47
|
+
- currentState: 현재 상태 1줄 요약 (필수)
|
|
48
|
+
- recentFiles: 수정한 파일 목록 (최대 10개)
|
|
49
|
+
- blockers: 발견한 블로커/이슈
|
|
50
|
+
- verification: 마지막 검증 결과
|
|
51
|
+
- architectureDecision: 새로운 아키텍처 결정
|
|
52
|
+
- codePattern: 새로운 코드 패턴
|
|
53
|
+
- techStack: 기술 스택 업데이트
|
|
54
|
+
다음 세션에서 이 컨텍스트가 자동 복원됨.`,
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
project: { type: 'string', description: '프로젝트명' },
|
|
59
|
+
currentState: { type: 'string', description: '현재 상태 (1줄 요약)' },
|
|
60
|
+
recentFiles: { type: 'array', items: { type: 'string' }, description: '수정한 파일 목록' },
|
|
61
|
+
blockers: { type: 'string', description: '블로커/이슈' },
|
|
62
|
+
verification: { type: 'string', enum: ['passed', 'failed'], description: '검증 결과' },
|
|
63
|
+
architectureDecision: { type: 'string', description: '새 아키텍처 결정' },
|
|
64
|
+
codePattern: { type: 'string', description: '새 코드 패턴' },
|
|
65
|
+
techStack: { type: 'object', additionalProperties: { type: 'string' }, description: '기술 스택 업데이트' }
|
|
66
|
+
},
|
|
67
|
+
required: ['project', 'currentState']
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'session_summary',
|
|
72
|
+
description: `현재 프로젝트의 컨텍스트 요약 조회.
|
|
73
|
+
- 토큰 추정치 포함
|
|
74
|
+
- 전체 컨텍스트 스냅샷 반환
|
|
75
|
+
세션 중간에 컨텍스트 확인 시 사용.`,
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
project: { type: 'string', description: '프로젝트명' }
|
|
80
|
+
},
|
|
81
|
+
required: ['project']
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
// ===== 핸들러 =====
|
|
86
|
+
export async function handleSessionStart(args) {
|
|
87
|
+
return logger.withTool('session_start', async () => {
|
|
88
|
+
const parsed = SessionStartSchema.safeParse(args);
|
|
89
|
+
if (!parsed.success) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
92
|
+
isError: true
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const { project, compact } = parsed.data;
|
|
96
|
+
const startTime = performance.now();
|
|
97
|
+
try {
|
|
98
|
+
// 세션 시작 기록
|
|
99
|
+
recordSessionStart(project);
|
|
100
|
+
if (compact) {
|
|
101
|
+
// 간결한 요약 반환
|
|
102
|
+
const summary = await getCompactContext(project);
|
|
103
|
+
const elapsed = performance.now() - startTime;
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: `${summary}\n\n---\n_Loaded in ${elapsed.toFixed(2)}ms_`
|
|
108
|
+
}]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// 전체 컨텍스트 반환
|
|
112
|
+
const context = await loadContext(project);
|
|
113
|
+
const elapsed = performance.now() - startTime;
|
|
114
|
+
return {
|
|
115
|
+
content: [{
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
...context,
|
|
119
|
+
_meta: {
|
|
120
|
+
loadTimeMs: parseFloat(elapsed.toFixed(2)),
|
|
121
|
+
timestamp: new Date().toISOString()
|
|
122
|
+
}
|
|
123
|
+
}, null, 2)
|
|
124
|
+
}]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger.error('Session start failed', { project, error: String(error) });
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: 'text', text: `Session start failed: ${error}` }],
|
|
131
|
+
isError: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}, args);
|
|
135
|
+
}
|
|
136
|
+
export async function handleSessionEnd(args) {
|
|
137
|
+
return logger.withTool('session_end', async () => {
|
|
138
|
+
const parsed = SessionEndSchema.safeParse(args);
|
|
139
|
+
if (!parsed.success) {
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
142
|
+
isError: true
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const { project, currentState, recentFiles, blockers, verification, architectureDecision, codePattern, techStack } = parsed.data;
|
|
146
|
+
try {
|
|
147
|
+
// 컨텍스트 저장
|
|
148
|
+
await saveContext(project, {
|
|
149
|
+
currentState,
|
|
150
|
+
recentFiles,
|
|
151
|
+
blockers,
|
|
152
|
+
verification,
|
|
153
|
+
architectureDecision,
|
|
154
|
+
codePattern,
|
|
155
|
+
techStack
|
|
156
|
+
});
|
|
157
|
+
// 세션 종료 기록
|
|
158
|
+
recordSessionEnd(project, currentState, verification);
|
|
159
|
+
// 스냅샷 생성 (토큰 추정 포함)
|
|
160
|
+
const snapshot = await createContextSnapshot(project);
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: JSON.stringify({
|
|
165
|
+
success: true,
|
|
166
|
+
project,
|
|
167
|
+
saved: {
|
|
168
|
+
currentState,
|
|
169
|
+
recentFilesCount: recentFiles?.length || 0,
|
|
170
|
+
hasBlockers: !!blockers,
|
|
171
|
+
verification: verification || null,
|
|
172
|
+
newDecision: !!architectureDecision,
|
|
173
|
+
newPattern: !!codePattern,
|
|
174
|
+
techStackUpdated: !!techStack
|
|
175
|
+
},
|
|
176
|
+
tokenEstimate: snapshot.tokenEstimate,
|
|
177
|
+
timestamp: snapshot.timestamp
|
|
178
|
+
}, null, 2)
|
|
179
|
+
}]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
logger.error('Session end failed', { project, error: String(error) });
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: 'text', text: `Session end failed: ${error}` }],
|
|
186
|
+
isError: true
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}, args);
|
|
190
|
+
}
|
|
191
|
+
export async function handleSessionSummary(args) {
|
|
192
|
+
return logger.withTool('session_summary', async () => {
|
|
193
|
+
const parsed = SessionSummarySchema.safeParse(args);
|
|
194
|
+
if (!parsed.success) {
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
197
|
+
isError: true
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const { project } = parsed.data;
|
|
201
|
+
try {
|
|
202
|
+
const snapshot = await createContextSnapshot(project);
|
|
203
|
+
return {
|
|
204
|
+
content: [{
|
|
205
|
+
type: 'text',
|
|
206
|
+
text: JSON.stringify(snapshot, null, 2)
|
|
207
|
+
}]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
logger.error('Session summary failed', { project, error: String(error) });
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: 'text', text: `Session summary failed: ${error}` }],
|
|
214
|
+
isError: true
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}, args);
|
|
218
|
+
}
|
|
219
|
+
// ===== 세션 기록 헬퍼 =====
|
|
220
|
+
function recordSessionStart(project) {
|
|
221
|
+
try {
|
|
222
|
+
const stmt = db.prepare(`
|
|
223
|
+
INSERT INTO sessions (project, last_work, current_status)
|
|
224
|
+
VALUES (?, 'Session started', 'in_progress')
|
|
225
|
+
`);
|
|
226
|
+
stmt.run(project);
|
|
227
|
+
invalidateProjects();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
logger.warn('Failed to record session start', { project, error: String(error) });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function recordSessionEnd(project, lastWork, verification) {
|
|
234
|
+
try {
|
|
235
|
+
// 마지막 in_progress 세션 업데이트
|
|
236
|
+
const stmt = db.prepare(`
|
|
237
|
+
UPDATE sessions
|
|
238
|
+
SET last_work = ?,
|
|
239
|
+
current_status = 'completed',
|
|
240
|
+
verification_result = ?,
|
|
241
|
+
timestamp = CURRENT_TIMESTAMP
|
|
242
|
+
WHERE project = ? AND current_status = 'in_progress'
|
|
243
|
+
ORDER BY id DESC
|
|
244
|
+
LIMIT 1
|
|
245
|
+
`);
|
|
246
|
+
stmt.run(lastWork, verification || null, project);
|
|
247
|
+
invalidateProjects();
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
logger.warn('Failed to record session end', { project, error: String(error) });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// 컨텍스트 도구 (context_get, context_update)
|
|
2
|
+
// 프로젝트 연속성의 핵심 - 세션 간 컨텍스트 유지
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { db, APPS_DIR } from '../db/database.js';
|
|
5
|
+
import { readFileContent, parseMarkdownTable } from '../utils/helpers.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { ContextGetSchema, ContextUpdateSchema } from '../schemas.js';
|
|
8
|
+
// ===== 도구 정의 =====
|
|
9
|
+
export const contextTools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'context_get',
|
|
12
|
+
description: `프로젝트 컨텍스트 조회. 새 세션 시작 시 필수 호출.
|
|
13
|
+
- 고정 컨텍스트: 기술 스택, 아키텍처 결정, 코드 패턴
|
|
14
|
+
- 활성 컨텍스트: 현재 상태, 최근 파일, 블로커
|
|
15
|
+
- 미완료 태스크 (최대 3개)
|
|
16
|
+
~650토큰으로 압축된 프로젝트 정보 반환.`,
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
project: { type: 'string', description: '프로젝트명' }
|
|
21
|
+
},
|
|
22
|
+
required: ['project']
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'context_update',
|
|
27
|
+
description: `프로젝트 컨텍스트 업데이트. 작업 종료 시 호출.
|
|
28
|
+
- currentState: 현재 상태 1줄 요약 (필수)
|
|
29
|
+
- recentFiles: 최근 수정 파일 (최대 10개)
|
|
30
|
+
- blockers: 블로커/이슈 (없으면 생략)
|
|
31
|
+
- verification: 마지막 검증 결과 (passed/failed)
|
|
32
|
+
- architectureDecision: 새로운 아키텍처 결정`,
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
project: { type: 'string', description: '프로젝트명' },
|
|
37
|
+
currentState: { type: 'string', description: '현재 상태 (1줄 요약)' },
|
|
38
|
+
recentFiles: { type: 'array', items: { type: 'string' }, description: '최근 수정 파일' },
|
|
39
|
+
blockers: { type: 'string', description: '블로커/이슈' },
|
|
40
|
+
verification: { type: 'string', enum: ['passed', 'failed'], description: '검증 결과' },
|
|
41
|
+
architectureDecision: { type: 'string', description: '추가할 아키텍처 결정' }
|
|
42
|
+
},
|
|
43
|
+
required: ['project', 'currentState']
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
// ===== 핸들러 =====
|
|
48
|
+
export async function handleContextGet(args) {
|
|
49
|
+
return logger.withTool('context_get', async () => {
|
|
50
|
+
// 입력 검증
|
|
51
|
+
const parsed = ContextGetSchema.safeParse(args);
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
55
|
+
isError: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const { project } = parsed.data;
|
|
59
|
+
// Layer 1: 고정 컨텍스트
|
|
60
|
+
const projectContextStmt = db.prepare('SELECT * FROM project_context WHERE project = ?');
|
|
61
|
+
const projectContext = projectContextStmt.get(project);
|
|
62
|
+
// Layer 2: 활성 컨텍스트
|
|
63
|
+
const activeContextStmt = db.prepare('SELECT * FROM active_context WHERE project = ?');
|
|
64
|
+
const activeContext = activeContextStmt.get(project);
|
|
65
|
+
// Layer 3: 미완료 태스크 (최대 3개)
|
|
66
|
+
const tasksStmt = db.prepare(`
|
|
67
|
+
SELECT id, title, status, priority
|
|
68
|
+
FROM tasks
|
|
69
|
+
WHERE project = ? AND status IN ('pending', 'in_progress')
|
|
70
|
+
ORDER BY priority DESC, created_at DESC
|
|
71
|
+
LIMIT 3
|
|
72
|
+
`);
|
|
73
|
+
const tasks = tasksStmt.all(project);
|
|
74
|
+
// 고정 컨텍스트가 없으면 plan.md에서 자동 추출
|
|
75
|
+
let techStack = {};
|
|
76
|
+
if (!projectContext) {
|
|
77
|
+
const planPath = path.join(APPS_DIR, project, 'plan.md');
|
|
78
|
+
const planContent = await readFileContent(planPath);
|
|
79
|
+
if (planContent) {
|
|
80
|
+
techStack = parseMarkdownTable(planContent, '기술 스택');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const result = {
|
|
84
|
+
project,
|
|
85
|
+
fixed: {
|
|
86
|
+
techStack: projectContext?.tech_stack ? JSON.parse(projectContext.tech_stack) : techStack,
|
|
87
|
+
architectureDecisions: projectContext?.architecture_decisions ? JSON.parse(projectContext.architecture_decisions) : [],
|
|
88
|
+
codePatterns: projectContext?.code_patterns ? JSON.parse(projectContext.code_patterns) : [],
|
|
89
|
+
specialNotes: projectContext?.special_notes || null
|
|
90
|
+
},
|
|
91
|
+
active: {
|
|
92
|
+
currentState: activeContext?.current_state || 'No active context',
|
|
93
|
+
recentFiles: activeContext?.recent_files ? JSON.parse(activeContext.recent_files) : [],
|
|
94
|
+
blockers: activeContext?.blockers || null,
|
|
95
|
+
lastVerification: activeContext?.last_verification || null,
|
|
96
|
+
updatedAt: activeContext?.updated_at || null
|
|
97
|
+
},
|
|
98
|
+
pendingTasks: tasks.map(t => ({
|
|
99
|
+
id: t.id,
|
|
100
|
+
title: t.title,
|
|
101
|
+
status: t.status,
|
|
102
|
+
priority: t.priority
|
|
103
|
+
}))
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
content: [{
|
|
107
|
+
type: 'text',
|
|
108
|
+
text: JSON.stringify(result, null, 2)
|
|
109
|
+
}]
|
|
110
|
+
};
|
|
111
|
+
}, args);
|
|
112
|
+
}
|
|
113
|
+
export async function handleContextUpdate(args) {
|
|
114
|
+
return logger.withTool('context_update', async () => {
|
|
115
|
+
// 입력 검증
|
|
116
|
+
const parsed = ContextUpdateSchema.safeParse(args);
|
|
117
|
+
if (!parsed.success) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
120
|
+
isError: true
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const { project, currentState, recentFiles, blockers, verification, architectureDecision } = parsed.data;
|
|
124
|
+
// 활성 컨텍스트 업데이트
|
|
125
|
+
const stmt = db.prepare(`
|
|
126
|
+
INSERT OR REPLACE INTO active_context (project, current_state, recent_files, blockers, last_verification, updated_at)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
128
|
+
`);
|
|
129
|
+
stmt.run(project, currentState, recentFiles ? JSON.stringify(recentFiles.slice(0, 10)) : null, blockers || null, verification || null);
|
|
130
|
+
// 아키텍처 결정 추가 (있으면)
|
|
131
|
+
if (architectureDecision) {
|
|
132
|
+
const getStmt = db.prepare('SELECT architecture_decisions FROM project_context WHERE project = ?');
|
|
133
|
+
const row = getStmt.get(project);
|
|
134
|
+
let decisions = [];
|
|
135
|
+
if (row?.architecture_decisions) {
|
|
136
|
+
try {
|
|
137
|
+
decisions = JSON.parse(row.architecture_decisions);
|
|
138
|
+
}
|
|
139
|
+
catch { /* ignore */ }
|
|
140
|
+
}
|
|
141
|
+
decisions.unshift(architectureDecision);
|
|
142
|
+
decisions = decisions.slice(0, 5);
|
|
143
|
+
const updateStmt = db.prepare(`
|
|
144
|
+
INSERT INTO project_context (project, architecture_decisions, updated_at)
|
|
145
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
146
|
+
ON CONFLICT(project) DO UPDATE SET
|
|
147
|
+
architecture_decisions = ?,
|
|
148
|
+
updated_at = CURRENT_TIMESTAMP
|
|
149
|
+
`);
|
|
150
|
+
const decisionsJson = JSON.stringify(decisions);
|
|
151
|
+
updateStmt.run(project, decisionsJson, decisionsJson);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
content: [{
|
|
155
|
+
type: 'text',
|
|
156
|
+
text: JSON.stringify({
|
|
157
|
+
success: true,
|
|
158
|
+
project,
|
|
159
|
+
updated: {
|
|
160
|
+
currentState,
|
|
161
|
+
recentFiles: recentFiles?.length || 0,
|
|
162
|
+
hasBlockers: !!blockers,
|
|
163
|
+
verification: verification || null,
|
|
164
|
+
architectureDecision: architectureDecision || null
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}]
|
|
168
|
+
};
|
|
169
|
+
}, args);
|
|
170
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// 임베딩 도구 (rebuild_embeddings)
|
|
2
|
+
// 시맨틱 검색용 임베딩 관리
|
|
3
|
+
import { db } from '../db/database.js';
|
|
4
|
+
import { generateEmbedding, embeddingToBuffer } from '../utils/embedding.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
// ===== 도구 정의 =====
|
|
7
|
+
export const embeddingTools = [
|
|
8
|
+
{
|
|
9
|
+
name: 'rebuild_embeddings',
|
|
10
|
+
description: `임베딩 재생성.
|
|
11
|
+
- force=false: 누락된 임베딩만 생성
|
|
12
|
+
- force=true: 전체 임베딩 재생성
|
|
13
|
+
|
|
14
|
+
시맨틱 검색 품질 개선에 사용.
|
|
15
|
+
배치 처리로 메모리 효율적.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
force: { type: 'boolean', description: '전체 재생성 (기본: false)' }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
// ===== 핸들러 =====
|
|
25
|
+
export async function handleRebuildEmbeddings(args) {
|
|
26
|
+
return logger.withTool('rebuild_embeddings', async () => {
|
|
27
|
+
const force = args?.force || false;
|
|
28
|
+
// 대상 메모리 조회
|
|
29
|
+
let sql;
|
|
30
|
+
if (force) {
|
|
31
|
+
sql = 'SELECT id, content FROM memories ORDER BY id';
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
sql = `
|
|
35
|
+
SELECT m.id, m.content FROM memories m
|
|
36
|
+
LEFT JOIN embeddings e ON m.id = e.memory_id
|
|
37
|
+
WHERE e.memory_id IS NULL
|
|
38
|
+
ORDER BY m.id
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
const memories = db.prepare(sql).all();
|
|
42
|
+
if (memories.length === 0) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: JSON.stringify({
|
|
47
|
+
success: true,
|
|
48
|
+
message: 'No embeddings to rebuild',
|
|
49
|
+
processed: 0
|
|
50
|
+
})
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
logger.info('Starting embedding rebuild', {
|
|
55
|
+
total: memories.length,
|
|
56
|
+
force
|
|
57
|
+
}, 'rebuild_embeddings');
|
|
58
|
+
// 배치 처리
|
|
59
|
+
const BATCH_SIZE = 10;
|
|
60
|
+
let processed = 0;
|
|
61
|
+
let failed = 0;
|
|
62
|
+
for (let i = 0; i < memories.length; i += BATCH_SIZE) {
|
|
63
|
+
const batch = memories.slice(i, i + BATCH_SIZE);
|
|
64
|
+
await Promise.all(batch.map(async (memory) => {
|
|
65
|
+
try {
|
|
66
|
+
const embedding = await generateEmbedding(memory.content);
|
|
67
|
+
if (embedding) {
|
|
68
|
+
const stmt = db.prepare(`
|
|
69
|
+
INSERT OR REPLACE INTO embeddings (memory_id, embedding, created_at)
|
|
70
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
71
|
+
`);
|
|
72
|
+
stmt.run(memory.id, embeddingToBuffer(embedding));
|
|
73
|
+
processed++;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
failed++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
failed++;
|
|
81
|
+
logger.error('Embedding generation failed', {
|
|
82
|
+
memoryId: memory.id,
|
|
83
|
+
error: String(e)
|
|
84
|
+
}, 'rebuild_embeddings');
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
// 진행 상황 로깅
|
|
88
|
+
if ((i + BATCH_SIZE) % 50 === 0 || i + BATCH_SIZE >= memories.length) {
|
|
89
|
+
logger.info('Embedding progress', {
|
|
90
|
+
processed,
|
|
91
|
+
failed,
|
|
92
|
+
remaining: memories.length - i - BATCH_SIZE
|
|
93
|
+
}, 'rebuild_embeddings');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 최종 통계
|
|
97
|
+
const totalMemories = db.prepare('SELECT COUNT(*) as count FROM memories').get().count;
|
|
98
|
+
const totalEmbeddings = db.prepare('SELECT COUNT(*) as count FROM embeddings').get().count;
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: JSON.stringify({
|
|
103
|
+
success: true,
|
|
104
|
+
processed,
|
|
105
|
+
failed,
|
|
106
|
+
coverage: `${Math.round((totalEmbeddings / totalMemories) * 100)}%`,
|
|
107
|
+
stats: {
|
|
108
|
+
totalMemories,
|
|
109
|
+
totalEmbeddings
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}]
|
|
113
|
+
};
|
|
114
|
+
}, args);
|
|
115
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { handleContextGet, handleContextUpdate } from './context.js';
|
|
2
|
+
import { handleMemoryStore, handleMemorySearch, handleMemoryDelete, handleMemoryStats } from './memory.js';
|
|
3
|
+
import { handleTaskManage } from './task.js';
|
|
4
|
+
import { handleVerify } from './verify.js';
|
|
5
|
+
import { handleLearn, handleRecallSolution } from './learn.js';
|
|
6
|
+
import { handleProjects } from './projects.js';
|
|
7
|
+
import { handleRebuildEmbeddings } from './embedding.js';
|
|
8
|
+
import { handleSessionStart, handleSessionEnd, handleSessionSummary } from './auto-capture.js';
|
|
9
|
+
import type { Tool, CallToolResult } from '../types.js';
|
|
10
|
+
export declare const allToolsV2: Tool[];
|
|
11
|
+
export declare function handleToolV2(name: string, args: unknown): Promise<CallToolResult>;
|
|
12
|
+
export declare const toolNamesV2: string[];
|
|
13
|
+
export { handleContextGet, handleContextUpdate, handleMemoryStore, handleMemorySearch, handleMemoryDelete, handleMemoryStats, handleTaskManage, handleVerify, handleLearn, handleRecallSolution, handleProjects, handleRebuildEmbeddings, handleSessionStart, handleSessionEnd, handleSessionSummary };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// 통합 도구 v2 - 15개 도구
|
|
2
|
+
// 기존 46개 도구를 15개로 통합 (자동 컨텍스트 캡처 3개 추가)
|
|
3
|
+
import { contextTools, handleContextGet, handleContextUpdate } from './context.js';
|
|
4
|
+
import { memoryTools, handleMemoryStore, handleMemorySearch, handleMemoryDelete, handleMemoryStats } from './memory.js';
|
|
5
|
+
import { taskTools, handleTaskManage } from './task.js';
|
|
6
|
+
import { verifyTools, handleVerify } from './verify.js';
|
|
7
|
+
import { learnTools, handleLearn, handleRecallSolution } from './learn.js';
|
|
8
|
+
import { projectsTools, handleProjects } from './projects.js';
|
|
9
|
+
import { embeddingTools, handleRebuildEmbeddings } from './embedding.js';
|
|
10
|
+
import { autoCaptureTools, handleSessionStart, handleSessionEnd, handleSessionSummary } from './auto-capture.js';
|
|
11
|
+
// 모든 도구 정의 (15개)
|
|
12
|
+
export const allToolsV2 = [
|
|
13
|
+
...contextTools, // context_get, context_update
|
|
14
|
+
...memoryTools, // memory_store, memory_search, memory_delete, memory_stats
|
|
15
|
+
...taskTools, // task_manage
|
|
16
|
+
...verifyTools, // verify
|
|
17
|
+
...learnTools, // learn, recall_solution
|
|
18
|
+
...projectsTools, // projects
|
|
19
|
+
...embeddingTools, // rebuild_embeddings
|
|
20
|
+
...autoCaptureTools // session_start, session_end, session_summary
|
|
21
|
+
];
|
|
22
|
+
// 도구 핸들러 라우터
|
|
23
|
+
export async function handleToolV2(name, args) {
|
|
24
|
+
switch (name) {
|
|
25
|
+
// Context
|
|
26
|
+
case 'context_get':
|
|
27
|
+
return handleContextGet(args);
|
|
28
|
+
case 'context_update':
|
|
29
|
+
return handleContextUpdate(args);
|
|
30
|
+
// Memory
|
|
31
|
+
case 'memory_store':
|
|
32
|
+
return handleMemoryStore(args);
|
|
33
|
+
case 'memory_search':
|
|
34
|
+
return handleMemorySearch(args);
|
|
35
|
+
case 'memory_delete':
|
|
36
|
+
return handleMemoryDelete(args);
|
|
37
|
+
case 'memory_stats':
|
|
38
|
+
return handleMemoryStats();
|
|
39
|
+
// Task
|
|
40
|
+
case 'task_manage':
|
|
41
|
+
return handleTaskManage(args);
|
|
42
|
+
// Verify
|
|
43
|
+
case 'verify':
|
|
44
|
+
return handleVerify(args);
|
|
45
|
+
// Learn
|
|
46
|
+
case 'learn':
|
|
47
|
+
return handleLearn(args);
|
|
48
|
+
case 'recall_solution':
|
|
49
|
+
return handleRecallSolution(args);
|
|
50
|
+
// Projects
|
|
51
|
+
case 'projects':
|
|
52
|
+
return handleProjects(args);
|
|
53
|
+
// Embedding
|
|
54
|
+
case 'rebuild_embeddings':
|
|
55
|
+
return handleRebuildEmbeddings(args);
|
|
56
|
+
// Auto Capture (Session Lifecycle)
|
|
57
|
+
case 'session_start':
|
|
58
|
+
return handleSessionStart(args);
|
|
59
|
+
case 'session_end':
|
|
60
|
+
return handleSessionEnd(args);
|
|
61
|
+
case 'session_summary':
|
|
62
|
+
return handleSessionSummary(args);
|
|
63
|
+
default:
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
66
|
+
isError: true
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 도구 이름 목록
|
|
71
|
+
export const toolNamesV2 = allToolsV2.map(t => t.name);
|
|
72
|
+
// Export individual handlers for testing
|
|
73
|
+
export { handleContextGet, handleContextUpdate, handleMemoryStore, handleMemorySearch, handleMemoryDelete, handleMemoryStats, handleTaskManage, handleVerify, handleLearn, handleRecallSolution, handleProjects, handleRebuildEmbeddings, handleSessionStart, handleSessionEnd, handleSessionSummary };
|