claude-session-continuity-mcp 1.7.0 β 1.8.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 +43 -2
- package/dist/hooks/user-prompt-submit.js +135 -5
- package/dist/index.js +19 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# claude-session-continuity-mcp (v1.
|
|
1
|
+
# claude-session-continuity-mcp (v1.8.0)
|
|
2
2
|
|
|
3
3
|
> **Zero Re-explanation Session Continuity for Claude Code** β Automatic context capture + semantic search
|
|
4
4
|
|
|
@@ -100,7 +100,7 @@ npm install claude-session-continuity-mcp
|
|
|
100
100
|
| Hook | Command | Function |
|
|
101
101
|
|------|---------|----------|
|
|
102
102
|
| `SessionStart` | `claude-hook-session-start` | Auto-loads project context on session start |
|
|
103
|
-
| `UserPromptSubmit` | `claude-hook-user-prompt` | Auto-injects relevant memories
|
|
103
|
+
| `UserPromptSubmit` | `claude-hook-user-prompt` | Auto-injects relevant memories + past reference search |
|
|
104
104
|
| `PostToolUse` | `claude-hook-post-tool` | Tracks file changes (Edit, Write) automatically |
|
|
105
105
|
| `PreCompact` | `claude-hook-pre-compact` | Saves important context before compression |
|
|
106
106
|
| `Stop` | `claude-hook-session-end` | Auto-saves session on exit (no manual call needed) |
|
|
@@ -142,6 +142,8 @@ After installation, restart Claude Code to activate the hooks.
|
|
|
142
142
|
| πΎ **Auto Backup** | **(v1.5.0)** Daily SQLite backup (max 5) |
|
|
143
143
|
| π‘οΈ **PreCompact Save** | **(v1.5.0)** Save context before compression |
|
|
144
144
|
| πͺ **Auto Session End** | **(v1.5.0)** No manual session_end needed |
|
|
145
|
+
| π **Past Reference Detection** | **(v1.8.0)** "μ λ²μ X μ΄λ»κ² νμ΄?" auto-searches DB |
|
|
146
|
+
| π **User Directive Extraction** | **(v1.8.0)** Auto-extracts "always/never" rules from prompts |
|
|
145
147
|
|
|
146
148
|
---
|
|
147
149
|
|
|
@@ -195,6 +197,42 @@ npx claude-session-hooks uninstall
|
|
|
195
197
|
export MCP_HOOKS_DISABLED=true
|
|
196
198
|
```
|
|
197
199
|
|
|
200
|
+
### Past Reference Detection (v1.8.0)
|
|
201
|
+
|
|
202
|
+
When you ask about past work, the `UserPromptSubmit` hook automatically searches the database:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
You: "μ λ²μ μΈμ±κ²°μ μ΄λ»κ² νμ΄?"
|
|
206
|
+
β Hook detects "μ λ²μ" + extracts keyword "μΈμ±κ²°μ "
|
|
207
|
+
β Searches sessions, memories (FTS5), and solutions
|
|
208
|
+
β Injects matching results into context automatically
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Supported patterns (Korean & English):**
|
|
212
|
+
|
|
213
|
+
| Pattern | Example |
|
|
214
|
+
|---------|---------|
|
|
215
|
+
| μ λ²μ/μ μ/μ΄μ μ ... μ΄λ»κ² | "μ λ²μ CORS μλ¬ μ΄λ»κ² ν΄κ²°νμ§?" |
|
|
216
|
+
| ~νλ/λ§λ€μλ/ν΄κ²°νλ | "μμ νλ λ‘κ·ΈμΈ λ‘μ§" |
|
|
217
|
+
| μ§λ μΈμ
/μμ
μμ | "μ§λ μΈμ
μμ κ²°μ ꡬν" |
|
|
218
|
+
| last time/before/previously | "How did we handle auth last time?" |
|
|
219
|
+
| did we/did I ... before | "Did we fix the database migration before?" |
|
|
220
|
+
| remember when/recall when | "Remember when we set up CI?" |
|
|
221
|
+
|
|
222
|
+
**Output example:**
|
|
223
|
+
```markdown
|
|
224
|
+
## Related Past Work (auto-detected from your question)
|
|
225
|
+
|
|
226
|
+
### Sessions
|
|
227
|
+
- [2/14] μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ μ±ν€ μμ , μΈμ±κ²°μ IAP νλ‘μ° μμ
|
|
228
|
+
|
|
229
|
+
### Memories
|
|
230
|
+
- π― [decision] ν
μ€νΈ: μΈμ±κ²°μ μν λ±λ‘ μλ£
|
|
231
|
+
|
|
232
|
+
### Solutions
|
|
233
|
+
- **IAP_BILLING_ERROR**: StoreKit 2 migrationμΌλ‘ ν΄κ²°
|
|
234
|
+
```
|
|
235
|
+
|
|
198
236
|
### Why npm exec? (v1.4.3+)
|
|
199
237
|
|
|
200
238
|
Previous versions used absolute paths or `npx`:
|
|
@@ -455,6 +493,9 @@ npm run test:coverage
|
|
|
455
493
|
- [x] Auto-migrate legacy hooks (v1.6.1)
|
|
456
494
|
- [x] Fix PostToolUse matcher format to string (v1.6.3)
|
|
457
495
|
- [x] Fix README documentation for new hook format (v1.6.4)
|
|
496
|
+
- [x] Empty session skip and techStack save improvements (v1.7.1)
|
|
497
|
+
- [x] Past reference auto-detection in UserPromptSubmit hook (v1.8.0)
|
|
498
|
+
- [x] User directive extraction ("always/never" rules) (v1.8.0)
|
|
458
499
|
- [ ] sqlite-vec native vector search (v2 - when data > 1000 records)
|
|
459
500
|
- [ ] Web dashboard
|
|
460
501
|
- [ ] Cloud sync option
|
|
@@ -31,6 +31,123 @@ function getProject(cwd, workspaceRoot) {
|
|
|
31
31
|
}
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
|
+
// ===== κ³Όκ±° μ°Έμ‘° μλ κ°μ§ =====
|
|
35
|
+
const PAST_REFERENCE_PATTERNS = [
|
|
36
|
+
// νκ΅μ΄
|
|
37
|
+
/(?:μ λ²μ|μ μ|μ΄μ μ|κ·Έλ|μ§λλ²μ|μμ μ|μκΉ)\s+(.+?)(?:\s*(?:μ΄λ»κ²|λ|무μ|μ|μ΄λ|μΈμ ))/,
|
|
38
|
+
/(?:νλ|νμλ|λ§λ€μλ|μμ νλ|ꡬννλ|ν΄κ²°νλ)\s*(.+)/,
|
|
39
|
+
/(?:μ§λ|μ΄μ |μ )\s*(?:μΈμ
|μμ
|μκ°|λ²).*?(?:μμ|λ)\s*(.+)/,
|
|
40
|
+
// μμ΄
|
|
41
|
+
/(?:last time|before|previously|earlier)\s+(?:.*?)\s*((?:how|what|why|where|when).*)/i,
|
|
42
|
+
/(?:did we|did I|have we|have I)\s+(.+)\s+(?:before|last time|earlier)/i,
|
|
43
|
+
/(?:remember when|recall when)\s+(.+)/i,
|
|
44
|
+
];
|
|
45
|
+
function extractPastKeywords(prompt) {
|
|
46
|
+
for (const pattern of PAST_REFERENCE_PATTERNS) {
|
|
47
|
+
const match = prompt.match(pattern);
|
|
48
|
+
if (match?.[1]) {
|
|
49
|
+
// μΆμΆλ ν€μλμμ μ‘°μ¬/μλ¬Έμ¬ μ κ±°, ν΅μ¬ λ¨μ΄λ§
|
|
50
|
+
return match[1].trim().replace(/[?οΌ\s]+$/g, '').slice(0, 50);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function searchPastWork(db, keyword) {
|
|
56
|
+
const result = { sessions: [], memories: [], solutions: [] };
|
|
57
|
+
const likeKeyword = `%${keyword}%`;
|
|
58
|
+
// 1. sessions κ²μ (μ΅κ·Ό 30μΌ, μμ 3건)
|
|
59
|
+
try {
|
|
60
|
+
const sessions = db.prepare(`
|
|
61
|
+
SELECT last_work, timestamp FROM sessions
|
|
62
|
+
WHERE last_work LIKE ?
|
|
63
|
+
AND last_work != 'Session ended'
|
|
64
|
+
AND last_work != 'Session work completed'
|
|
65
|
+
AND last_work != 'Session started'
|
|
66
|
+
AND last_work != ''
|
|
67
|
+
AND timestamp > datetime('now', '-30 days')
|
|
68
|
+
ORDER BY timestamp DESC LIMIT 3
|
|
69
|
+
`).all(likeKeyword);
|
|
70
|
+
for (const s of sessions) {
|
|
71
|
+
const work = s.last_work.length > 80 ? s.last_work.slice(0, 80) + '...' : s.last_work;
|
|
72
|
+
result.sessions.push({ date: s.timestamp?.slice(0, 10) || 'unknown', work });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch { /* ignore */ }
|
|
76
|
+
// 2. memories FTS5 κ²μ (μμ 2건)
|
|
77
|
+
try {
|
|
78
|
+
const memories = db.prepare(`
|
|
79
|
+
SELECT m.content, m.memory_type FROM memories m
|
|
80
|
+
JOIN memories_fts fts ON m.id = fts.rowid
|
|
81
|
+
WHERE memories_fts MATCH ?
|
|
82
|
+
ORDER BY rank LIMIT 2
|
|
83
|
+
`).all(keyword);
|
|
84
|
+
for (const m of memories) {
|
|
85
|
+
const content = m.content.length > 80 ? m.content.slice(0, 80) + '...' : m.content;
|
|
86
|
+
result.memories.push({ type: m.memory_type, content });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// FTS5 λ§€μΉ μ€ν¨ μ LIKE ν΄λ°±
|
|
91
|
+
try {
|
|
92
|
+
const memories = db.prepare(`
|
|
93
|
+
SELECT content, memory_type FROM memories
|
|
94
|
+
WHERE content LIKE ?
|
|
95
|
+
ORDER BY importance DESC, created_at DESC LIMIT 2
|
|
96
|
+
`).all(likeKeyword);
|
|
97
|
+
for (const m of memories) {
|
|
98
|
+
const content = m.content.length > 80 ? m.content.slice(0, 80) + '...' : m.content;
|
|
99
|
+
result.memories.push({ type: m.memory_type, content });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { /* ignore */ }
|
|
103
|
+
}
|
|
104
|
+
// 3. solutions κ²μ (μμ 2건)
|
|
105
|
+
try {
|
|
106
|
+
const solutions = db.prepare(`
|
|
107
|
+
SELECT error_signature, solution FROM solutions
|
|
108
|
+
WHERE error_signature LIKE ? OR solution LIKE ?
|
|
109
|
+
ORDER BY created_at DESC LIMIT 2
|
|
110
|
+
`).all(likeKeyword, likeKeyword);
|
|
111
|
+
for (const s of solutions) {
|
|
112
|
+
const sol = s.solution.length > 80 ? s.solution.slice(0, 80) + '...' : s.solution;
|
|
113
|
+
result.solutions.push({ signature: s.error_signature, solution: sol });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch { /* ignore */ }
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function formatPastWork(pastWork) {
|
|
120
|
+
const { sessions, memories, solutions } = pastWork;
|
|
121
|
+
if (sessions.length === 0 && memories.length === 0 && solutions.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
const lines = ['## Related Past Work (auto-detected from your question)\n'];
|
|
124
|
+
if (sessions.length > 0) {
|
|
125
|
+
lines.push('### Sessions');
|
|
126
|
+
for (const s of sessions) {
|
|
127
|
+
lines.push(`- [${s.date}] ${s.work}`);
|
|
128
|
+
}
|
|
129
|
+
lines.push('');
|
|
130
|
+
}
|
|
131
|
+
if (memories.length > 0) {
|
|
132
|
+
const typeIcons = {
|
|
133
|
+
observation: 'π', decision: 'π―', learning: 'π', error: 'β οΈ', pattern: 'π'
|
|
134
|
+
};
|
|
135
|
+
lines.push('### Memories');
|
|
136
|
+
for (const m of memories) {
|
|
137
|
+
const icon = typeIcons[m.type] || 'π';
|
|
138
|
+
lines.push(`- ${icon} [${m.type}] ${m.content}`);
|
|
139
|
+
}
|
|
140
|
+
lines.push('');
|
|
141
|
+
}
|
|
142
|
+
if (solutions.length > 0) {
|
|
143
|
+
lines.push('### Solutions');
|
|
144
|
+
for (const s of solutions) {
|
|
145
|
+
lines.push(`- **${s.signature}**: ${s.solution}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
34
151
|
// ===== μ¬μ©μ μ§μμ¬ν μλ μΆμΆ =====
|
|
35
152
|
const DIRECTIVE_PATTERNS = [
|
|
36
153
|
{ pattern: /(?:μ λ|never)\s+(.+)/i, priority: 'high' },
|
|
@@ -80,7 +197,7 @@ function extractAndSaveDirectives(dbPath, project, prompt) {
|
|
|
80
197
|
// ν
μ΄λΈ λ―Έμ‘΄μ¬ λ± λ¬΄μ
|
|
81
198
|
}
|
|
82
199
|
}
|
|
83
|
-
function loadContext(dbPath, project) {
|
|
200
|
+
function loadContext(dbPath, project, prompt) {
|
|
84
201
|
if (!fs.existsSync(dbPath))
|
|
85
202
|
return null;
|
|
86
203
|
try {
|
|
@@ -175,19 +292,30 @@ function loadContext(dbPath, project) {
|
|
|
175
292
|
lines.push('');
|
|
176
293
|
}
|
|
177
294
|
// μ΅κ·Ό μλ¬ μ루μ
|
|
178
|
-
const
|
|
295
|
+
const recentSolutions = db.prepare(`
|
|
179
296
|
SELECT error_signature, solution FROM solutions
|
|
180
297
|
WHERE project = ?
|
|
181
298
|
ORDER BY created_at DESC LIMIT 3
|
|
182
299
|
`).all(project);
|
|
183
|
-
if (
|
|
300
|
+
if (recentSolutions.length > 0) {
|
|
184
301
|
lines.push('## π§ Recent Error Solutions');
|
|
185
|
-
for (const s of
|
|
302
|
+
for (const s of recentSolutions) {
|
|
186
303
|
const sol = s.solution.length > 80 ? s.solution.slice(0, 80) + '...' : s.solution;
|
|
187
304
|
lines.push(`- **${s.error_signature}**: ${sol}`);
|
|
188
305
|
}
|
|
189
306
|
lines.push('');
|
|
190
307
|
}
|
|
308
|
+
// κ³Όκ±° μ°Έμ‘° μλ κ²μ (ν둬ννΈμμ κ³Όκ±° μ°Έμ‘° ν¨ν΄ κ°μ§ μ)
|
|
309
|
+
if (prompt) {
|
|
310
|
+
const keyword = extractPastKeywords(prompt);
|
|
311
|
+
if (keyword) {
|
|
312
|
+
const pastWork = searchPastWork(db, keyword);
|
|
313
|
+
const pastSection = formatPastWork(pastWork);
|
|
314
|
+
if (pastSection) {
|
|
315
|
+
lines.push(pastSection);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
191
319
|
db.close();
|
|
192
320
|
lines.push('---');
|
|
193
321
|
lines.push('_Auto-injected by MCP v5. Use `session_end` when done._');
|
|
@@ -228,16 +356,18 @@ async function main() {
|
|
|
228
356
|
}
|
|
229
357
|
const dbPath = path.join(workspaceRoot, '.claude', 'sessions.db');
|
|
230
358
|
// μ¬μ©μ ν둬ννΈμμ μ§μμ¬ν μΆμΆ
|
|
359
|
+
let userPrompt;
|
|
231
360
|
if (inputData) {
|
|
232
361
|
try {
|
|
233
362
|
const parsed = JSON.parse(inputData);
|
|
234
363
|
if (parsed.prompt) {
|
|
364
|
+
userPrompt = parsed.prompt;
|
|
235
365
|
extractAndSaveDirectives(dbPath, project, parsed.prompt);
|
|
236
366
|
}
|
|
237
367
|
}
|
|
238
368
|
catch { /* ignore */ }
|
|
239
369
|
}
|
|
240
|
-
const context = loadContext(dbPath, project);
|
|
370
|
+
const context = loadContext(dbPath, project, userPrompt);
|
|
241
371
|
if (context) {
|
|
242
372
|
console.log(`\n<project-context project="${project}">\n${context}\n</project-context>\n`);
|
|
243
373
|
}
|
package/dist/index.js
CHANGED
|
@@ -830,9 +830,15 @@ async function handleTool(name, args) {
|
|
|
830
830
|
const rawModifiedFiles = args.modifiedFiles || args.recentFiles;
|
|
831
831
|
const modifiedFiles = Array.isArray(rawModifiedFiles) ? rawModifiedFiles : undefined;
|
|
832
832
|
const blockers = args.blockers;
|
|
833
|
+
const techStack = args.techStack;
|
|
833
834
|
if (!project) {
|
|
834
835
|
return { content: [{ type: 'text', text: 'Error: project is required' }] };
|
|
835
836
|
}
|
|
837
|
+
// λΉ μΈμ
λ°©μ§
|
|
838
|
+
const emptyPatterns = ['Session ended', 'Session work completed', 'Session started', ''];
|
|
839
|
+
if (emptyPatterns.includes(summary) && (!modifiedFiles || modifiedFiles.length === 0)) {
|
|
840
|
+
return { content: [{ type: 'text', text: `βοΈ Skipped empty session for ${project}` }] };
|
|
841
|
+
}
|
|
836
842
|
// μΈμ
μ μ₯
|
|
837
843
|
db.prepare(`
|
|
838
844
|
INSERT INTO sessions (project, last_work, current_status, next_tasks, modified_files, issues)
|
|
@@ -843,7 +849,19 @@ async function handleTool(name, args) {
|
|
|
843
849
|
INSERT OR REPLACE INTO active_context (project, current_state, recent_files, blockers, updated_at)
|
|
844
850
|
VALUES (?, ?, ?, ?, datetime('now'))
|
|
845
851
|
`).run(project, summary, modifiedFiles ? JSON.stringify(modifiedFiles) : null, blockers || null);
|
|
846
|
-
|
|
852
|
+
// techStack μ μ₯ (μμΌλ©΄)
|
|
853
|
+
if (techStack && Object.keys(techStack).length > 0) {
|
|
854
|
+
const existing = db.prepare('SELECT tech_stack FROM project_context WHERE project = ?').get(project);
|
|
855
|
+
let merged = existing?.tech_stack ? JSON.parse(existing.tech_stack) : {};
|
|
856
|
+
merged = { ...merged, ...techStack };
|
|
857
|
+
const json = JSON.stringify(merged);
|
|
858
|
+
db.prepare(`
|
|
859
|
+
INSERT INTO project_context (project, tech_stack, updated_at)
|
|
860
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
|
861
|
+
ON CONFLICT(project) DO UPDATE SET tech_stack = ?, updated_at = CURRENT_TIMESTAMP
|
|
862
|
+
`).run(project, json, json);
|
|
863
|
+
}
|
|
864
|
+
return { content: [{ type: 'text', text: `β
Session saved for ${project}\nSummary: ${summary}\nWork: ${workDone || summary}\nFiles: ${modifiedFiles?.length || 0}\nBlockers: ${blockers || 'none'}${techStack ? '\nTech stack updated' : ''}` }] };
|
|
847
865
|
}
|
|
848
866
|
case 'session_history': {
|
|
849
867
|
const project = args.project;
|