claude-cortex 1.8.3 → 1.9.1
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 +30 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/setup/doctor.d.ts +5 -0
- package/dist/setup/doctor.d.ts.map +1 -0
- package/dist/setup/doctor.js +141 -0
- package/dist/setup/doctor.js.map +1 -0
- package/dist/setup/hooks.d.ts.map +1 -1
- package/dist/setup/hooks.js +2 -1
- package/dist/setup/hooks.js.map +1 -1
- package/package.json +1 -1
- package/scripts/session-end-hook.mjs +548 -0
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
|
|
@@ -317,6 +343,8 @@ npx claude-cortex service status # Check service status
|
|
|
317
343
|
npx claude-cortex clawdbot install # Install Clawdbot/Moltbot hook manually
|
|
318
344
|
npx claude-cortex clawdbot uninstall # Remove Clawdbot/Moltbot hook
|
|
319
345
|
npx claude-cortex clawdbot status # Check Clawdbot hook status
|
|
346
|
+
npx claude-cortex doctor # Check installation health
|
|
347
|
+
npx claude-cortex --version # Show version
|
|
320
348
|
```
|
|
321
349
|
|
|
322
350
|
Works on **macOS** (launchd), **Linux** (systemd), and **Windows** (Startup folder). The dashboard and API server will start automatically on login.
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
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
|
|
@@ -32,6 +33,9 @@ import { handleServiceCommand } from './service/install.js';
|
|
|
32
33
|
import { setupClaudeMd } from './setup/claude-md.js';
|
|
33
34
|
import { handleHookCommand } from './setup/hooks.js';
|
|
34
35
|
import { handleClawdbotCommand } from './setup/clawdbot.js';
|
|
36
|
+
import { createRequire } from 'module';
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
38
|
+
const pkg = require('../package.json');
|
|
35
39
|
// Get the directory of this file for relative paths
|
|
36
40
|
const __filename = fileURLToPath(import.meta.url);
|
|
37
41
|
const __dirname = path.dirname(__filename);
|
|
@@ -129,6 +133,17 @@ function startDashboard() {
|
|
|
129
133
|
* Main entry point
|
|
130
134
|
*/
|
|
131
135
|
async function main() {
|
|
136
|
+
// Handle --version / -v
|
|
137
|
+
if (process.argv[2] === '--version' || process.argv[2] === '-v') {
|
|
138
|
+
console.log(pkg.version);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Handle "doctor" subcommand
|
|
142
|
+
if (process.argv[2] === 'doctor') {
|
|
143
|
+
const { handleDoctorCommand } = await import('./setup/doctor.js');
|
|
144
|
+
await handleDoctorCommand();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
132
147
|
// Handle "setup" subcommand
|
|
133
148
|
if (process.argv[2] === 'setup') {
|
|
134
149
|
await setupClaudeMd();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/setup/doctor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuIH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BzD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx claude-cortex doctor` — diagnostic checks for Cortex installation health.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const results = [];
|
|
11
|
+
function add(status, message) {
|
|
12
|
+
results.push({ status, message });
|
|
13
|
+
}
|
|
14
|
+
function getDbPath() {
|
|
15
|
+
const newPath = path.join(os.homedir(), '.claude-cortex', 'memories.db');
|
|
16
|
+
const legacyPath = path.join(os.homedir(), '.claude-memory', 'memories.db');
|
|
17
|
+
if (fs.existsSync(newPath))
|
|
18
|
+
return { path: newPath, isLegacy: false };
|
|
19
|
+
if (fs.existsSync(legacyPath))
|
|
20
|
+
return { path: legacyPath, isLegacy: true };
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function formatBytes(bytes) {
|
|
24
|
+
if (bytes < 1024)
|
|
25
|
+
return `${bytes} B`;
|
|
26
|
+
if (bytes < 1024 * 1024)
|
|
27
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
28
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
29
|
+
}
|
|
30
|
+
function checkNode() {
|
|
31
|
+
const major = parseInt(process.version.slice(1), 10);
|
|
32
|
+
if (major >= 18) {
|
|
33
|
+
add('PASS', `Node.js ${process.version} (>= 18 required)`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
add('WARN', `Node.js ${process.version} — version 18+ recommended`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function checkDatabase() {
|
|
40
|
+
const db = getDbPath();
|
|
41
|
+
if (!db) {
|
|
42
|
+
add('FAIL', 'Database not found at ~/.claude-cortex/memories.db');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const label = db.isLegacy ? '~/.claude-memory/memories.db (legacy)' : '~/.claude-cortex/memories.db';
|
|
46
|
+
try {
|
|
47
|
+
const stat = fs.statSync(db.path);
|
|
48
|
+
const Database = require('better-sqlite3');
|
|
49
|
+
const conn = new Database(db.path, { readonly: true });
|
|
50
|
+
const row = conn.prepare('SELECT COUNT(*) as count FROM memories').get();
|
|
51
|
+
conn.close();
|
|
52
|
+
add('PASS', `Database: ${label} (${row.count} memories, ${formatBytes(stat.size)})`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
add('FAIL', `Database: ${label} — ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function checkClaudeMd() {
|
|
59
|
+
const claudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
60
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
61
|
+
add('WARN', 'CLAUDE.md not found — run `npx claude-cortex setup`');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
65
|
+
if (content.includes('# Claude Cortex')) {
|
|
66
|
+
add('PASS', 'CLAUDE.md: Cortex instructions present');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
add('WARN', 'CLAUDE.md: Cortex instructions not found — run `npx claude-cortex setup`');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function checkHooks() {
|
|
73
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
74
|
+
if (!fs.existsSync(settingsPath)) {
|
|
75
|
+
add('WARN', 'settings.json not found — hooks not configured');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
let settings;
|
|
79
|
+
try {
|
|
80
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
add('FAIL', 'settings.json: failed to parse');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const hooks = settings?.hooks || {};
|
|
87
|
+
const expected = ['PreCompact', 'SessionStart', 'SessionEnd'];
|
|
88
|
+
for (const name of expected) {
|
|
89
|
+
const entries = hooks[name];
|
|
90
|
+
const hasCortex = Array.isArray(entries) && entries.some((e) => Array.isArray(e?.hooks) && e.hooks.some((h) => typeof h?.command === 'string' && h.command.includes('claude-cortex')));
|
|
91
|
+
if (hasCortex) {
|
|
92
|
+
add('PASS', `Hook: ${name} configured`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
add('WARN', `Hook: ${name} not configured`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function checkMcp() {
|
|
100
|
+
// Check project-level .mcp.json
|
|
101
|
+
const projectMcp = path.join(process.cwd(), '.mcp.json');
|
|
102
|
+
// Check user-level config
|
|
103
|
+
const userMcp = path.join(os.homedir(), '.claude.json');
|
|
104
|
+
for (const p of [projectMcp, userMcp]) {
|
|
105
|
+
if (fs.existsSync(p)) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(p, 'utf-8');
|
|
108
|
+
if (content.includes('claude-cortex')) {
|
|
109
|
+
add('PASS', `MCP: cortex configured in ${path.basename(p)}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch { /* ignore parse errors */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
add('WARN', 'MCP: no cortex entry found in .mcp.json or ~/.claude.json');
|
|
117
|
+
}
|
|
118
|
+
export async function handleDoctorCommand() {
|
|
119
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
120
|
+
const __dirname = path.dirname(__filename);
|
|
121
|
+
const pkg = require(path.resolve(__dirname, '..', '..', 'package.json'));
|
|
122
|
+
console.log(`\nClaude Cortex Doctor v${pkg.version}\n`);
|
|
123
|
+
checkNode();
|
|
124
|
+
checkDatabase();
|
|
125
|
+
checkClaudeMd();
|
|
126
|
+
checkHooks();
|
|
127
|
+
checkMcp();
|
|
128
|
+
// Print results
|
|
129
|
+
for (const r of results) {
|
|
130
|
+
const tag = r.status === 'PASS' ? '\x1b[32m PASS\x1b[0m'
|
|
131
|
+
: r.status === 'WARN' ? '\x1b[33m WARN\x1b[0m'
|
|
132
|
+
: '\x1b[31m FAIL\x1b[0m';
|
|
133
|
+
console.log(`${tag} ${r.message}`);
|
|
134
|
+
}
|
|
135
|
+
const passed = results.filter(r => r.status === 'PASS').length;
|
|
136
|
+
const warns = results.filter(r => r.status === 'WARN').length;
|
|
137
|
+
const fails = results.filter(r => r.status === 'FAIL').length;
|
|
138
|
+
console.log(`\n${passed} passed, ${warns} warnings, ${fails} failed\n`);
|
|
139
|
+
process.exit(fails > 0 ? 1 : 0);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/setup/doctor.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;
|
|
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"}
|
package/dist/setup/hooks.js
CHANGED
|
@@ -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);
|
package/dist/setup/hooks.js.map
CHANGED
|
@@ -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;
|
|
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
|
@@ -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
|
+
});
|