claude-cortex 1.8.3 → 1.9.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
@@ -77,7 +77,6 @@ Add to `~/.claude/settings.json` for automatic memory extraction and context loa
77
77
  "hooks": {
78
78
  "PreCompact": [
79
79
  {
80
- "matcher": "",
81
80
  "hooks": [
82
81
  {
83
82
  "type": "command",
@@ -89,7 +88,6 @@ Add to `~/.claude/settings.json` for automatic memory extraction and context loa
89
88
  ],
90
89
  "SessionStart": [
91
90
  {
92
- "matcher": "",
93
91
  "hooks": [
94
92
  {
95
93
  "type": "command",
@@ -98,6 +96,17 @@ Add to `~/.claude/settings.json` for automatic memory extraction and context loa
98
96
  }
99
97
  ]
100
98
  }
99
+ ],
100
+ "SessionEnd": [
101
+ {
102
+ "hooks": [
103
+ {
104
+ "type": "command",
105
+ "command": "npx -y claude-cortex hook session-end",
106
+ "timeout": 10
107
+ }
108
+ ]
109
+ }
101
110
  ]
102
111
  }
103
112
  }
@@ -105,6 +114,7 @@ Add to `~/.claude/settings.json` for automatic memory extraction and context loa
105
114
 
106
115
  - **PreCompact**: Auto-saves important context before compaction events
107
116
  - **SessionStart**: Auto-loads project context at the start of each session
117
+ - **SessionEnd**: Auto-saves context when the session exits
108
118
 
109
119
  ### 4. Run Setup (Recommended)
110
120
 
@@ -246,6 +256,22 @@ Claude: Let me check my memory.
246
256
  > Found: "Using PostgreSQL for the database" (architecture, 95% salience)
247
257
  ```
248
258
 
259
+ ## Hook Coverage
260
+
261
+ Claude Cortex uses three hooks to cover the full session lifecycle:
262
+
263
+ | Hook | Fires When | What It Does | Reliability |
264
+ |------|-----------|--------------|-------------|
265
+ | **SessionStart** | Session begins | Loads project context from memory | Reliable |
266
+ | **PreCompact** | Before context compaction | Extracts important content before context is lost | Reliable (primary safety net) |
267
+ | **SessionEnd** | Session terminates | Extracts important content on exit | Best-effort* |
268
+
269
+ *SessionEnd does not fire on forced termination (terminal killed, SSH drops, crash). PreCompact remains the primary safety net since compaction happens more frequently than session exits.
270
+
271
+ ### Stop Hook (Opt-in, Future)
272
+
273
+ A prompt-based Stop hook that uses Haiku to evaluate each Claude response for important events is planned. This calls the Haiku API on every response, which adds latency and cost. It will be opt-in via `--with-stop-hook` flag.
274
+
249
275
  ## Configuration
250
276
 
251
277
  ### Database Location
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * npx claude-cortex setup # Configure Claude for proactive memory use
16
16
  * npx claude-cortex hook pre-compact # Run pre-compact hook (for settings.json)
17
17
  * npx claude-cortex hook session-start # Run session-start hook (for settings.json)
18
+ * npx claude-cortex hook session-end # Run session-end hook (for settings.json)
18
19
  * npx claude-cortex service install # Auto-start dashboard on login
19
20
  * npx claude-cortex service uninstall # Remove auto-start
20
21
  * npx claude-cortex service status # Check service status
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@
15
15
  * npx claude-cortex setup # Configure Claude for proactive memory use
16
16
  * npx claude-cortex hook pre-compact # Run pre-compact hook (for settings.json)
17
17
  * npx claude-cortex hook session-start # Run session-start hook (for settings.json)
18
+ * npx claude-cortex hook session-end # Run session-end hook (for settings.json)
18
19
  * npx claude-cortex service install # Auto-start dashboard on login
19
20
  * npx claude-cortex service uninstall # Remove auto-start
20
21
  * npx claude-cortex service status # Check service status
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/setup/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiBH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvE"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/setup/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvE"}
@@ -12,13 +12,14 @@ const SCRIPTS_DIR = path.resolve(__dirname, '..', '..', 'scripts');
12
12
  const HOOKS = {
13
13
  'pre-compact': 'pre-compact-hook.mjs',
14
14
  'session-start': 'session-start-hook.mjs',
15
+ 'session-end': 'session-end-hook.mjs',
15
16
  };
16
17
  export async function handleHookCommand(hookName) {
17
18
  const scriptFile = HOOKS[hookName];
18
19
  if (!scriptFile) {
19
20
  console.error(`Unknown hook: ${hookName}`);
20
21
  console.log(`Available hooks: ${Object.keys(HOOKS).join(', ')}`);
21
- console.log('Usage: claude-cortex hook <pre-compact|session-start>');
22
+ console.log('Usage: claude-cortex hook <pre-compact|session-start|session-end>');
22
23
  process.exit(1);
23
24
  }
24
25
  const scriptPath = path.join(SCRIPTS_DIR, scriptFile);
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/setup/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,qDAAqD;AACrD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;AAEnE,MAAM,KAAK,GAA2B;IACpC,aAAa,EAAE,sBAAsB;IACrC,eAAe,EAAE,wBAAwB;CAC1C,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE;QAClD,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC;KACtC,CAAC,CAAC;IAEH,kCAAkC;IAClC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEhC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/setup/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,qDAAqD;AACrD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;AAEnE,MAAM,KAAK,GAA2B;IACpC,aAAa,EAAE,sBAAsB;IACrC,eAAe,EAAE,wBAAwB;IACzC,aAAa,EAAE,sBAAsB;CACtC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,iBAAiB,QAAQ,EAAE,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE;QAClD,KAAK,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC;KACtC,CAAC,CAAC;IAEH,kCAAkC;IAClC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEhC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cortex",
3
- "version": "1.8.3",
3
+ "version": "1.9.0",
4
4
  "description": "Brain-like memory system for Claude Code - solves context compaction and memory persistence",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -0,0 +1,548 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session-end hook for Claude Cortex - Automatic Memory Extraction on Exit
4
+ *
5
+ * This script runs when a Claude Code session ends and:
6
+ * 1. Reads the session transcript from the JSONL file
7
+ * 2. Analyzes conversation content for important information
8
+ * 3. Auto-extracts high-salience items (decisions, patterns, errors, etc.)
9
+ * 4. Saves them to the memory database automatically
10
+ *
11
+ * NOTE: SessionEnd doesn't always fire reliably (e.g. terminal killed, SSH drops).
12
+ * PreCompact remains the primary safety net for context preservation.
13
+ *
14
+ * Input (stdin JSON):
15
+ * {
16
+ * "session_id": "abc123",
17
+ * "transcript_path": "~/.claude/projects/.../abc.jsonl",
18
+ * "cwd": "/path/to/project",
19
+ * "hook_event_name": "SessionEnd",
20
+ * "reason": "exit" | "clear" | "logout" | "prompt_input_exit" | "other"
21
+ * }
22
+ */
23
+
24
+ import Database from 'better-sqlite3';
25
+ import { existsSync, mkdirSync, readFileSync } from 'fs';
26
+ import { join } from 'path';
27
+ import { homedir } from 'os';
28
+
29
+ // Database paths (with legacy fallback)
30
+ const NEW_DB_DIR = join(homedir(), '.claude-cortex');
31
+ const LEGACY_DB_DIR = join(homedir(), '.claude-memory');
32
+
33
+ function getDbPath() {
34
+ const newPath = join(NEW_DB_DIR, 'memories.db');
35
+ const legacyPath = join(LEGACY_DB_DIR, 'memories.db');
36
+ if (existsSync(newPath) || !existsSync(legacyPath)) {
37
+ return { dir: NEW_DB_DIR, path: newPath };
38
+ }
39
+ return { dir: LEGACY_DB_DIR, path: legacyPath };
40
+ }
41
+
42
+ const { dir: DB_DIR, path: DB_PATH } = getDbPath();
43
+
44
+ // Memory limits
45
+ const MAX_SHORT_TERM_MEMORIES = 100;
46
+ const MAX_LONG_TERM_MEMORIES = 1000;
47
+ const BASE_THRESHOLD = 0.35;
48
+ const MAX_AUTO_MEMORIES = 5;
49
+
50
+ const CATEGORY_EXTRACTION_THRESHOLDS = {
51
+ architecture: 0.28,
52
+ error: 0.30,
53
+ context: 0.32,
54
+ learning: 0.32,
55
+ pattern: 0.35,
56
+ preference: 0.38,
57
+ note: 0.42,
58
+ todo: 0.40,
59
+ relationship: 0.35,
60
+ custom: 0.35,
61
+ };
62
+
63
+ // ==================== PROJECT DETECTION ====================
64
+
65
+ const SKIP_DIRECTORIES = [
66
+ 'src', 'lib', 'dist', 'build', 'out',
67
+ 'node_modules', '.git', '.next', '.cache',
68
+ 'test', 'tests', '__tests__', 'spec',
69
+ 'bin', 'scripts', 'config', 'public', 'static',
70
+ ];
71
+
72
+ function extractProjectFromPath(path) {
73
+ if (!path) return null;
74
+ const segments = path.split(/[/\\]/).filter(Boolean);
75
+ if (segments.length === 0) return null;
76
+ for (let i = segments.length - 1; i >= 0; i--) {
77
+ const segment = segments[i];
78
+ if (!SKIP_DIRECTORIES.includes(segment.toLowerCase())) {
79
+ if (segment.startsWith('.')) continue;
80
+ return segment;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ // ==================== DYNAMIC THRESHOLD ====================
87
+
88
+ function getMemoryStats(db) {
89
+ try {
90
+ const stats = db.prepare(`
91
+ SELECT
92
+ COUNT(*) as total,
93
+ SUM(CASE WHEN type = 'short_term' THEN 1 ELSE 0 END) as shortTerm,
94
+ SUM(CASE WHEN type = 'long_term' THEN 1 ELSE 0 END) as longTerm
95
+ FROM memories
96
+ `).get();
97
+ return stats || { total: 0, shortTerm: 0, longTerm: 0 };
98
+ } catch {
99
+ return { total: 0, shortTerm: 0, longTerm: 0 };
100
+ }
101
+ }
102
+
103
+ function getDynamicThreshold(memoryCount, maxMemories) {
104
+ const fullness = memoryCount / maxMemories;
105
+ if (fullness > 0.8) return 0.50;
106
+ if (fullness > 0.6) return 0.42;
107
+ if (fullness > 0.4) return 0.35;
108
+ if (fullness > 0.2) return 0.30;
109
+ return 0.25;
110
+ }
111
+
112
+ function getExtractionThreshold(category, dynamicThreshold) {
113
+ const categoryThreshold = CATEGORY_EXTRACTION_THRESHOLDS[category] || BASE_THRESHOLD;
114
+ return Math.min(categoryThreshold, dynamicThreshold);
115
+ }
116
+
117
+ // ==================== SALIENCE DETECTION ====================
118
+
119
+ const ARCHITECTURE_KEYWORDS = [
120
+ 'architecture', 'design', 'pattern', 'structure', 'system',
121
+ 'database', 'api', 'schema', 'model', 'framework', 'stack',
122
+ 'microservice', 'monolith', 'serverless', 'infrastructure'
123
+ ];
124
+
125
+ const ERROR_KEYWORDS = [
126
+ 'error', 'bug', 'fix', 'issue', 'problem', 'crash', 'fail',
127
+ 'exception', 'debug', 'resolve', 'solution', 'workaround'
128
+ ];
129
+
130
+ const PREFERENCE_KEYWORDS = [
131
+ 'prefer', 'always', 'never', 'style', 'convention', 'standard',
132
+ 'like', 'want', 'should', 'must', 'require'
133
+ ];
134
+
135
+ const PATTERN_KEYWORDS = [
136
+ 'pattern', 'practice', 'approach', 'method', 'technique',
137
+ 'implementation', 'strategy', 'algorithm', 'workflow'
138
+ ];
139
+
140
+ const DECISION_KEYWORDS = [
141
+ 'decided', 'decision', 'chose', 'chosen', 'selected', 'going with',
142
+ 'will use', 'opted for', 'settled on', 'agreed'
143
+ ];
144
+
145
+ const LEARNING_KEYWORDS = [
146
+ 'learned', 'discovered', 'realized', 'found out', 'turns out',
147
+ 'TIL', 'now know', 'understand now', 'figured out'
148
+ ];
149
+
150
+ const EMOTIONAL_MARKERS = [
151
+ 'important', 'critical', 'crucial', 'essential', 'key',
152
+ 'finally', 'breakthrough', 'eureka', 'aha', 'got it',
153
+ 'frustrating', 'annoying', 'tricky', 'remember'
154
+ ];
155
+
156
+ const CODE_REFERENCE_PATTERNS = [
157
+ /\b[A-Z][a-zA-Z]*\.[a-zA-Z]+\b/,
158
+ /\b[a-z_][a-zA-Z0-9_]*\.(ts|js|py|go|rs)\b/,
159
+ /`[^`]+`/,
160
+ /\b(function|class|interface|type|const|let|var)\s+\w+/i,
161
+ /\bline\s*\d+\b/i,
162
+ /\b(src|lib|app|components?)\/\S+/,
163
+ ];
164
+
165
+ function detectKeywords(text, keywords) {
166
+ const lower = text.toLowerCase();
167
+ return keywords.some(keyword => lower.includes(keyword.toLowerCase()));
168
+ }
169
+
170
+ function detectCodeReferences(content) {
171
+ return CODE_REFERENCE_PATTERNS.some(pattern => pattern.test(content));
172
+ }
173
+
174
+ function detectExplicitRequest(text) {
175
+ const patterns = [
176
+ /\bremember\s+(this|that)\b/i,
177
+ /\bdon'?t\s+forget\b/i,
178
+ /\bkeep\s+(in\s+)?mind\b/i,
179
+ /\bnote\s+(this|that)\b/i,
180
+ /\bsave\s+(this|that)\b/i,
181
+ /\bimportant[:\s]/i,
182
+ /\bfor\s+future\s+reference\b/i,
183
+ ];
184
+ return patterns.some(pattern => pattern.test(text));
185
+ }
186
+
187
+ function calculateSalience(text) {
188
+ let score = 0.25;
189
+ if (detectExplicitRequest(text)) score += 0.5;
190
+ if (detectKeywords(text, ARCHITECTURE_KEYWORDS)) score += 0.4;
191
+ if (detectKeywords(text, ERROR_KEYWORDS)) score += 0.35;
192
+ if (detectKeywords(text, DECISION_KEYWORDS)) score += 0.35;
193
+ if (detectKeywords(text, LEARNING_KEYWORDS)) score += 0.3;
194
+ if (detectKeywords(text, PATTERN_KEYWORDS)) score += 0.25;
195
+ if (detectKeywords(text, PREFERENCE_KEYWORDS)) score += 0.25;
196
+ if (detectCodeReferences(text)) score += 0.15;
197
+ if (detectKeywords(text, EMOTIONAL_MARKERS)) score += 0.2;
198
+ return Math.min(1.0, score);
199
+ }
200
+
201
+ function suggestCategory(text) {
202
+ const lower = text.toLowerCase();
203
+ if (detectKeywords(lower, ARCHITECTURE_KEYWORDS)) return 'architecture';
204
+ if (detectKeywords(lower, ERROR_KEYWORDS)) return 'error';
205
+ if (detectKeywords(lower, DECISION_KEYWORDS)) return 'context';
206
+ if (detectKeywords(lower, LEARNING_KEYWORDS)) return 'learning';
207
+ if (detectKeywords(lower, PREFERENCE_KEYWORDS)) return 'preference';
208
+ if (detectKeywords(lower, PATTERN_KEYWORDS)) return 'pattern';
209
+ if (/\b(todo|fixme|hack|xxx)\b/i.test(lower)) return 'todo';
210
+ return 'note';
211
+ }
212
+
213
+ function extractTags(text, extractorName = null) {
214
+ const tags = new Set();
215
+ const hashtagMatches = text.match(/#[a-zA-Z][a-zA-Z0-9_-]*/g);
216
+ if (hashtagMatches) {
217
+ hashtagMatches.forEach(tag => tags.add(tag.slice(1).toLowerCase()));
218
+ }
219
+ const techTerms = [
220
+ 'react', 'vue', 'angular', 'node', 'python', 'typescript', 'javascript',
221
+ 'api', 'database', 'sql', 'mongodb', 'postgresql', 'mysql',
222
+ 'docker', 'kubernetes', 'aws', 'git', 'testing', 'auth', 'security'
223
+ ];
224
+ const lowerText = text.toLowerCase();
225
+ techTerms.forEach(term => {
226
+ if (lowerText.includes(term)) tags.add(term);
227
+ });
228
+ tags.add('auto-extracted');
229
+ tags.add('session-end');
230
+ if (extractorName) {
231
+ tags.add(`source:${extractorName}`);
232
+ }
233
+ return Array.from(tags).slice(0, 12);
234
+ }
235
+
236
+ function calculateFrequencyBoost(segment, allSegments) {
237
+ const commonWords = new Set([
238
+ 'about', 'after', 'before', 'being', 'between', 'could', 'during',
239
+ 'every', 'found', 'through', 'would', 'should', 'which', 'where',
240
+ 'there', 'these', 'their', 'other', 'using', 'because', 'without'
241
+ ]);
242
+ const words = segment.content.toLowerCase().split(/\s+/);
243
+ const keyTerms = words.filter(w =>
244
+ w.length > 5 && !commonWords.has(w) && /^[a-z]+$/.test(w)
245
+ );
246
+ let boost = 0;
247
+ const seenTerms = new Set();
248
+ for (const term of keyTerms) {
249
+ if (seenTerms.has(term)) continue;
250
+ seenTerms.add(term);
251
+ const mentions = allSegments.filter(s =>
252
+ s !== segment && s.content.toLowerCase().includes(term)
253
+ ).length;
254
+ if (mentions > 1) {
255
+ boost += 0.03 * Math.min(mentions, 5);
256
+ }
257
+ }
258
+ return Math.min(0.15, boost);
259
+ }
260
+
261
+ // ==================== CONTENT EXTRACTION ====================
262
+
263
+ function extractMemorableSegments(conversationText) {
264
+ const segments = [];
265
+ const extractors = [
266
+ {
267
+ name: 'decision',
268
+ patterns: [
269
+ /(?:we\s+)?decided\s+(?:to\s+)?(.{15,200})/gi,
270
+ /(?:going|went)\s+with\s+(.{15,150})/gi,
271
+ /(?:chose|chosen|selected)\s+(.{15,150})/gi,
272
+ /the\s+(?:approach|solution|fix)\s+(?:is|was)\s+(.{15,200})/gi,
273
+ /(?:using|will\s+use)\s+(.{15,150})/gi,
274
+ /(?:opted\s+for|settled\s+on)\s+(.{15,150})/gi,
275
+ ],
276
+ titlePrefix: 'Decision: ',
277
+ },
278
+ {
279
+ name: 'error-fix',
280
+ patterns: [
281
+ /(?:fixed|solved|resolved)\s+(?:by\s+)?(.{15,200})/gi,
282
+ /the\s+(?:fix|solution|workaround)\s+(?:is|was)\s+(.{15,200})/gi,
283
+ /(?:root\s+cause|issue)\s+(?:is|was)\s+(.{15,200})/gi,
284
+ /(?:error|bug)\s+(?:was\s+)?caused\s+by\s+(.{15,200})/gi,
285
+ /(?:problem|issue)\s+was\s+(.{15,150})/gi,
286
+ /(?:the\s+)?bug\s+(?:is|was)\s+(.{15,150})/gi,
287
+ /(?:debugging|debugged)\s+(.{15,150})/gi,
288
+ ],
289
+ titlePrefix: 'Fix: ',
290
+ },
291
+ {
292
+ name: 'learning',
293
+ patterns: [
294
+ /(?:learned|discovered|realized|found\s+out)\s+(?:that\s+)?(.{15,200})/gi,
295
+ /turns\s+out\s+(?:that\s+)?(.{15,200})/gi,
296
+ /(?:TIL|today\s+I\s+learned)[:\s]+(.{15,200})/gi,
297
+ /(?:now\s+)?(?:understand|know)\s+(?:that\s+)?(.{15,150})/gi,
298
+ /(?:figured\s+out|worked\s+out)\s+(.{15,150})/gi,
299
+ ],
300
+ titlePrefix: 'Learned: ',
301
+ },
302
+ {
303
+ name: 'architecture',
304
+ patterns: [
305
+ /the\s+architecture\s+(?:is|uses|consists\s+of)\s+(.{15,200})/gi,
306
+ /(?:design|pattern)\s+(?:is|uses)\s+(.{15,200})/gi,
307
+ /(?:system|api|database)\s+(?:structure|design)\s+(?:is|uses)\s+(.{15,200})/gi,
308
+ /(?:created|added|implemented|built)\s+(?:a\s+)?(.{15,200})/gi,
309
+ /(?:refactored|updated|changed)\s+(?:the\s+)?(.{15,150})/gi,
310
+ ],
311
+ titlePrefix: 'Architecture: ',
312
+ },
313
+ {
314
+ name: 'preference',
315
+ patterns: [
316
+ /(?:always|never)\s+(.{10,150})/gi,
317
+ /(?:prefer|want)\s+to\s+(.{10,150})/gi,
318
+ /(?:should|must)\s+(?:always\s+)?(.{10,150})/gi,
319
+ ],
320
+ titlePrefix: 'Preference: ',
321
+ },
322
+ {
323
+ name: 'important-note',
324
+ patterns: [
325
+ /important[:\s]+(.{15,200})/gi,
326
+ /(?:note|remember)[:\s]+(.{15,200})/gi,
327
+ /(?:key|critical)\s+(?:point|thing)[:\s]+(.{15,200})/gi,
328
+ /(?:this\s+is\s+)?(?:crucial|essential)[:\s]+(.{15,150})/gi,
329
+ /(?:don't\s+forget|keep\s+in\s+mind)[:\s]+(.{15,150})/gi,
330
+ ],
331
+ titlePrefix: 'Note: ',
332
+ },
333
+ ];
334
+
335
+ for (const extractor of extractors) {
336
+ for (const pattern of extractor.patterns) {
337
+ let match;
338
+ while ((match = pattern.exec(conversationText)) !== null) {
339
+ const content = match[1].trim();
340
+ if (content.length >= 20) {
341
+ const titleContent = content.slice(0, 50).replace(/\s+/g, ' ').trim();
342
+ const title = extractor.titlePrefix + (titleContent.length < 50 ? titleContent : titleContent + '...');
343
+ segments.push({
344
+ title,
345
+ content: content.slice(0, 500),
346
+ extractorType: extractor.name,
347
+ });
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ return segments;
354
+ }
355
+
356
+ function processSegments(segments, dynamicThreshold = BASE_THRESHOLD) {
357
+ const unique = [];
358
+ for (const seg of segments) {
359
+ const isDupe = unique.some(existing => {
360
+ const overlap = calculateOverlap(existing.content, seg.content);
361
+ return overlap > 0.8;
362
+ });
363
+ if (!isDupe) {
364
+ const text = seg.title + ' ' + seg.content;
365
+ const baseSalience = calculateSalience(text);
366
+ const category = suggestCategory(text);
367
+ unique.push({
368
+ ...seg,
369
+ baseSalience,
370
+ category,
371
+ tags: extractTags(text, seg.extractorType),
372
+ });
373
+ }
374
+ }
375
+
376
+ for (const seg of unique) {
377
+ const frequencyBoost = calculateFrequencyBoost(seg, unique);
378
+ seg.salience = Math.min(1.0, seg.baseSalience + frequencyBoost);
379
+ seg.frequencyBoost = frequencyBoost;
380
+ }
381
+
382
+ unique.sort((a, b) => b.salience - a.salience);
383
+
384
+ const filtered = unique.filter(seg => {
385
+ const threshold = getExtractionThreshold(seg.category, dynamicThreshold);
386
+ return seg.salience >= threshold;
387
+ });
388
+
389
+ return filtered.slice(0, MAX_AUTO_MEMORIES);
390
+ }
391
+
392
+ function calculateOverlap(text1, text2) {
393
+ const words1 = new Set(text1.toLowerCase().split(/\s+/));
394
+ const words2 = new Set(text2.toLowerCase().split(/\s+/));
395
+ const intersection = new Set([...words1].filter(w => words2.has(w)));
396
+ const union = new Set([...words1, ...words2]);
397
+ return intersection.size / union.size;
398
+ }
399
+
400
+ // ==================== DATABASE OPERATIONS ====================
401
+
402
+ function saveMemory(db, memory, project) {
403
+ const timestamp = new Date().toISOString();
404
+ const stmt = db.prepare(`
405
+ INSERT INTO memories (title, content, type, category, salience, tags, project, created_at, last_accessed)
406
+ VALUES (?, ?, 'short_term', ?, ?, ?, ?, ?, ?)
407
+ `);
408
+ stmt.run(
409
+ memory.title,
410
+ memory.content,
411
+ memory.category,
412
+ memory.salience,
413
+ JSON.stringify(memory.tags),
414
+ project || null,
415
+ timestamp,
416
+ timestamp
417
+ );
418
+ }
419
+
420
+ // ==================== TRANSCRIPT READING ====================
421
+
422
+ /**
423
+ * Read conversation text from the session transcript JSONL file.
424
+ */
425
+ function readTranscript(transcriptPath) {
426
+ if (!transcriptPath) return '';
427
+
428
+ // Expand ~ to homedir
429
+ const resolvedPath = transcriptPath.replace(/^~/, homedir());
430
+
431
+ if (!existsSync(resolvedPath)) {
432
+ console.error(`[session-end] Transcript not found: ${resolvedPath}`);
433
+ return '';
434
+ }
435
+
436
+ try {
437
+ const content = readFileSync(resolvedPath, 'utf-8');
438
+ const lines = content.trim().split('\n');
439
+
440
+ // Read last 50 lines to get recent conversation
441
+ const recentLines = lines.slice(-50);
442
+ const messages = [];
443
+
444
+ for (const line of recentLines) {
445
+ try {
446
+ const entry = JSON.parse(line);
447
+ const role = entry.type || entry.message?.role;
448
+ const msgContent = entry.message?.content;
449
+ if ((role === 'user' || role === 'assistant') && msgContent) {
450
+ const text = Array.isArray(msgContent)
451
+ ? msgContent.filter(c => c.type === 'text').map(c => c.text).join('\n')
452
+ : msgContent;
453
+ if (text && !text.startsWith('/')) {
454
+ messages.push(text);
455
+ }
456
+ }
457
+ } catch {
458
+ // Skip invalid lines
459
+ }
460
+ }
461
+
462
+ const result = messages.join('\n\n');
463
+ console.error(`[session-end] Read ${messages.length} messages from transcript (${result.length} chars)`);
464
+ return result;
465
+ } catch (err) {
466
+ console.error(`[session-end] Failed to read transcript: ${err.message}`);
467
+ return '';
468
+ }
469
+ }
470
+
471
+ // ==================== MAIN HOOK LOGIC ====================
472
+
473
+ let input = '';
474
+ process.stdin.setEncoding('utf8');
475
+
476
+ process.stdin.on('readable', () => {
477
+ let chunk;
478
+ while ((chunk = process.stdin.read()) !== null) {
479
+ input += chunk;
480
+ }
481
+ });
482
+
483
+ process.stdin.on('end', () => {
484
+ try {
485
+ const hookData = JSON.parse(input || '{}');
486
+
487
+ const reason = hookData.reason || 'unknown';
488
+ const project = extractProjectFromPath(hookData.cwd);
489
+
490
+ // Skip extraction on /clear — session is being intentionally wiped
491
+ if (reason === 'clear') {
492
+ console.error('[session-end] Session cleared, skipping extraction');
493
+ process.exit(0);
494
+ }
495
+
496
+ // Read conversation from transcript_path (provided by Claude Code)
497
+ const conversationText = readTranscript(hookData.transcript_path);
498
+
499
+ if (!conversationText || conversationText.length < 100) {
500
+ console.error('[session-end] Not enough conversation content to extract from');
501
+ process.exit(0);
502
+ }
503
+
504
+ // Ensure database directory exists
505
+ if (!existsSync(DB_DIR)) {
506
+ mkdirSync(DB_DIR, { recursive: true });
507
+ }
508
+
509
+ if (!existsSync(DB_PATH)) {
510
+ console.error('[session-end] Memory database not found, skipping extraction');
511
+ process.exit(0);
512
+ }
513
+
514
+ const db = new Database(DB_PATH, { timeout: 5000 });
515
+
516
+ const stats = getMemoryStats(db);
517
+ const totalMemories = stats.shortTerm + stats.longTerm;
518
+ const maxMemories = MAX_SHORT_TERM_MEMORIES + MAX_LONG_TERM_MEMORIES;
519
+ const dynamicThreshold = getDynamicThreshold(totalMemories, maxMemories);
520
+
521
+ console.error(`[session-end] Memory status: ${totalMemories}/${maxMemories} (${(totalMemories/maxMemories*100).toFixed(0)}% full)`);
522
+ console.error(`[session-end] Reason: ${reason}, Dynamic threshold: ${dynamicThreshold.toFixed(2)}`);
523
+
524
+ // Extract memorable segments
525
+ const segments = extractMemorableSegments(conversationText);
526
+ const processedSegments = processSegments(segments, dynamicThreshold);
527
+
528
+ let autoExtractedCount = 0;
529
+ for (const memory of processedSegments) {
530
+ try {
531
+ saveMemory(db, memory, project);
532
+ autoExtractedCount++;
533
+ const boostInfo = memory.frequencyBoost > 0 ? ` +${memory.frequencyBoost.toFixed(2)} boost` : '';
534
+ console.error(`[session-end] Saved: ${memory.title} (salience: ${memory.salience.toFixed(2)}${boostInfo}, category: ${memory.category})`);
535
+ } catch (err) {
536
+ console.error(`[session-end] Failed to save "${memory.title}": ${err.message}`);
537
+ }
538
+ }
539
+
540
+ console.error(`[session-end] Complete: ${autoExtractedCount} memories auto-extracted on session ${reason}`);
541
+
542
+ db.close();
543
+ process.exit(0);
544
+ } catch (error) {
545
+ console.error(`[session-end] Error: ${error.message}`);
546
+ process.exit(0); // Don't block session exit on errors
547
+ }
548
+ });