claude-mem 2.0.2
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 +31 -0
- package/README.md +79 -0
- package/claude-mem +0 -0
- package/hooks/pre-compact.js +139 -0
- package/hooks/session-end.js +157 -0
- package/hooks/session-start.js +195 -0
- package/package.json +37 -0
- package/src/claude-mem.js +859 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
CLAUDE-MEM SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Alex Newman (@thedotmack). All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software in its compiled/distributed form via npm, to use the software
|
|
7
|
+
for any purpose, subject to the following conditions:
|
|
8
|
+
|
|
9
|
+
1. USE RIGHTS: You may use the claude-mem CLI tool for personal or commercial
|
|
10
|
+
purposes without restriction.
|
|
11
|
+
|
|
12
|
+
2. NO SOURCE CODE RIGHTS: This license does NOT grant access to source code,
|
|
13
|
+
modification rights, or redistribution rights. The software is provided
|
|
14
|
+
as-is in its compiled form only.
|
|
15
|
+
|
|
16
|
+
3. NO REVERSE ENGINEERING: You may not reverse engineer, decompile, or
|
|
17
|
+
disassemble the software.
|
|
18
|
+
|
|
19
|
+
4. NO REDISTRIBUTION: You may not redistribute, repackage, or resell this
|
|
20
|
+
software. Users must install it from the official npm registry.
|
|
21
|
+
|
|
22
|
+
5. NO WARRANTY: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
23
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
24
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
25
|
+
|
|
26
|
+
6. LIMITATION OF LIABILITY: IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
27
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
|
28
|
+
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
|
29
|
+
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
30
|
+
|
|
31
|
+
For questions about this license, contact: thedotmack@gmail.com
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Claude Memory System
|
|
2
|
+
|
|
3
|
+
**Truth + Context = Clarity**
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
A professional CLI tool that transforms Claude Code conversation transcripts into a persistent, searchable knowledge graph. Never lose valuable context from your AI-assisted development sessions again.
|
|
7
|
+
|
|
8
|
+
## What is Claude-Mem?
|
|
9
|
+
|
|
10
|
+
Claude Memory System automatically:
|
|
11
|
+
- 🗜️ **Compresses** your Claude Code conversations into structured knowledge
|
|
12
|
+
- 🔍 **Extracts** key components, patterns, decisions, and fixes
|
|
13
|
+
- 📚 **Archives** sessions for future reference
|
|
14
|
+
- 🚀 **Loads** relevant context when you start new sessions
|
|
15
|
+
- ⚡ **Integrates** seamlessly with Claude Code via hooks
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install globally
|
|
21
|
+
npm install -g claude-mem
|
|
22
|
+
|
|
23
|
+
# Or use with npx (no install needed)
|
|
24
|
+
npx claude-mem install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
1. **Install the hooks into Claude Code:**
|
|
30
|
+
```bash
|
|
31
|
+
claude-mem install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Restart Claude Code** to load the new hooks
|
|
35
|
+
|
|
36
|
+
3. **Use `/compact` in Claude Code** to trigger memory compression
|
|
37
|
+
|
|
38
|
+
4. **Start new sessions** with automatic context loading
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
- `claude-mem install` - Set up Claude Code integration
|
|
43
|
+
- `claude-mem status` - Check system status
|
|
44
|
+
- `claude-mem compress <transcript>` - Manually compress a transcript
|
|
45
|
+
- `claude-mem load-context` - View stored memories
|
|
46
|
+
- `claude-mem logs` - View system logs
|
|
47
|
+
- `claude-mem uninstall` - Remove Claude Code hooks
|
|
48
|
+
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
1. When you use `/compact` in Claude Code, claude-mem automatically compresses your conversation
|
|
52
|
+
2. Entities (components, patterns, fixes, etc.) are extracted and stored
|
|
53
|
+
3. When you start a new Claude Code session, relevant context is automatically loaded
|
|
54
|
+
4. Your knowledge base grows smarter with each session
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Node.js 18.0 or higher
|
|
59
|
+
- Claude Code installed
|
|
60
|
+
- MCP memory server (automatically configured during install)
|
|
61
|
+
|
|
62
|
+
## Storage
|
|
63
|
+
|
|
64
|
+
Your compressed memories are stored in:
|
|
65
|
+
- `~/.claude-mem/index/` - Searchable index
|
|
66
|
+
- `~/.claude-mem/archives/` - Original transcripts
|
|
67
|
+
- `~/.claude-mem/hooks/` - Integration scripts
|
|
68
|
+
|
|
69
|
+
## Feedback
|
|
70
|
+
|
|
71
|
+
Please report issues or feedback at: https://github.com/thedotmack/claude-mem/issues
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
This software is free to use but is NOT open source. See LICENSE file for details.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
*Built with ❤️ for the Claude Code community*
|
package/claude-mem
ADDED
|
Binary file
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🔑 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
5
|
+
*
|
|
6
|
+
* OFFICIAL DOCS: Claude Code Hooks API v2025
|
|
7
|
+
* Last Verified: 2025-08-31
|
|
8
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
9
|
+
*
|
|
10
|
+
* Claude Memory System - Pre-Compact Hook
|
|
11
|
+
*
|
|
12
|
+
* CRITICAL REQUIREMENTS:
|
|
13
|
+
* - Hook responses MUST use 'continue' field (boolean)
|
|
14
|
+
* - When continue is false, MUST provide 'stopReason' field
|
|
15
|
+
* - DO NOT use 'decision' or 'reason' fields (deprecated pattern)
|
|
16
|
+
* - PreCompact hooks DO NOT support hookSpecificOutput field
|
|
17
|
+
* - Exit codes: 0=success, 1=error shown to user, 2=error with stderr shown
|
|
18
|
+
*
|
|
19
|
+
* Valid Response Format:
|
|
20
|
+
* Success: { "continue": true }
|
|
21
|
+
* Failure: { "continue": false, "stopReason": "error message" }
|
|
22
|
+
*
|
|
23
|
+
* @docs-ref: Official hook response format specification
|
|
24
|
+
* @see https://docs.anthropic.com/claude-code/hooks#response-format
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { spawn } from 'child_process';
|
|
28
|
+
import { join, dirname } from 'path';
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import { readFileSync, existsSync } from 'fs';
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
|
|
34
|
+
// Load configuration to get the CLI command name
|
|
35
|
+
let cliCommand = 'claude-mem'; // Default fallback
|
|
36
|
+
const configPath = join(__dirname, 'config.json');
|
|
37
|
+
if (existsSync(configPath)) {
|
|
38
|
+
try {
|
|
39
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
40
|
+
cliCommand = config.cliCommand || 'claude-mem';
|
|
41
|
+
} catch (e) {
|
|
42
|
+
// Fallback to default if config read fails
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function preCompactHook() {
|
|
47
|
+
let input = '';
|
|
48
|
+
|
|
49
|
+
// Read JSON input from stdin
|
|
50
|
+
process.stdin.on('data', chunk => {
|
|
51
|
+
input += chunk;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
process.stdin.on('end', async () => {
|
|
55
|
+
try {
|
|
56
|
+
const data = JSON.parse(input);
|
|
57
|
+
const transcriptPath = data.transcript_path;
|
|
58
|
+
|
|
59
|
+
if (!transcriptPath) {
|
|
60
|
+
// Output error in Claude Code expected format
|
|
61
|
+
// @docs-ref: continue: false with stopReason for blocking operations
|
|
62
|
+
console.log(JSON.stringify({
|
|
63
|
+
continue: false,
|
|
64
|
+
stopReason: "No transcript path provided"
|
|
65
|
+
}));
|
|
66
|
+
process.exit(2); // Exit 2 tells Claude Code to show error
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Call the CLI compress command using configured name
|
|
70
|
+
const compressor = spawn(cliCommand, ['compress', transcriptPath], {
|
|
71
|
+
stdio: ['ignore', 'pipe', 'pipe'] // Capture output instead of inherit
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let stdout = '';
|
|
75
|
+
let stderr = '';
|
|
76
|
+
|
|
77
|
+
compressor.stdout.on('data', (data) => {
|
|
78
|
+
stdout += data.toString();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
compressor.stderr.on('data', (data) => {
|
|
82
|
+
stderr += data.toString();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
compressor.on('close', (code) => {
|
|
86
|
+
if (code !== 0) {
|
|
87
|
+
// Check if it's the Claude Code executable error
|
|
88
|
+
if (stderr.includes('Claude Code executable not found')) {
|
|
89
|
+
// This is expected when running outside Claude Code context
|
|
90
|
+
// Just output success since we can't compress without Claude SDK
|
|
91
|
+
// @docs-ref: PreCompact hooks should only use continue field, not hookSpecificOutput
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
continue: true,
|
|
94
|
+
suppressOutput: true,
|
|
95
|
+
systemMessage: "Memory compression skipped - Claude SDK not available in this context"
|
|
96
|
+
}));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
} else {
|
|
99
|
+
// Real error - report it
|
|
100
|
+
// @docs-ref: Use continue: false with stopReason for errors
|
|
101
|
+
console.log(JSON.stringify({
|
|
102
|
+
continue: false,
|
|
103
|
+
stopReason: `Compression failed: ${stderr}`
|
|
104
|
+
}));
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// Success! Output confirmation
|
|
109
|
+
// @docs-ref: PreCompact hooks should only use continue field for success
|
|
110
|
+
console.log(JSON.stringify({
|
|
111
|
+
continue: true,
|
|
112
|
+
suppressOutput: true,
|
|
113
|
+
systemMessage: "Memory compression completed successfully"
|
|
114
|
+
}));
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
compressor.on('error', (error) => {
|
|
120
|
+
// @docs-ref: Use continue: false with stopReason for errors
|
|
121
|
+
console.log(JSON.stringify({
|
|
122
|
+
continue: false,
|
|
123
|
+
stopReason: `Failed to start compression: ${error.message}`
|
|
124
|
+
}));
|
|
125
|
+
process.exit(2);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// @docs-ref: Use continue: false with stopReason for errors
|
|
130
|
+
console.log(JSON.stringify({
|
|
131
|
+
continue: false,
|
|
132
|
+
stopReason: `Hook error: ${error.message}`
|
|
133
|
+
}));
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
preCompactHook();
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🔑 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
5
|
+
*
|
|
6
|
+
* OFFICIAL DOCS: Claude Code SessionEnd Hook v2025
|
|
7
|
+
* Last Verified: 2025-08-31
|
|
8
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
9
|
+
*
|
|
10
|
+
* Claude Memory System - Session End Hook
|
|
11
|
+
* Triggers compression when session ends with reason 'clear'
|
|
12
|
+
*
|
|
13
|
+
* SessionEnd Hook Payload:
|
|
14
|
+
* {
|
|
15
|
+
* "session_id": "string",
|
|
16
|
+
* "transcript_path": "string",
|
|
17
|
+
* "cwd": "string",
|
|
18
|
+
* "hook_event_name": "SessionEnd",
|
|
19
|
+
* "reason": "exit" | "clear" | "error" | ...
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Valid Response Format:
|
|
23
|
+
* Success: { "continue": true }
|
|
24
|
+
* Success with message: { "continue": true, "systemMessage": "string" }
|
|
25
|
+
* Failure: { "continue": false, "stopReason": "error message" }
|
|
26
|
+
*
|
|
27
|
+
* Note: SessionEnd hooks should generally always return continue: true
|
|
28
|
+
* to avoid blocking session termination.
|
|
29
|
+
*
|
|
30
|
+
* @docs-ref: SessionEnd hook should not block session ending
|
|
31
|
+
* @see https://docs.anthropic.com/claude-code/hooks#sessionend
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { spawn } from 'child_process';
|
|
35
|
+
import { join, dirname } from 'path';
|
|
36
|
+
import { fileURLToPath } from 'url';
|
|
37
|
+
import { readFileSync, existsSync } from 'fs';
|
|
38
|
+
|
|
39
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
|
|
41
|
+
// Load configuration to get the CLI command name
|
|
42
|
+
let cliCommand = 'claude-mem'; // Default fallback
|
|
43
|
+
const configPath = join(__dirname, 'config.json');
|
|
44
|
+
if (existsSync(configPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
47
|
+
cliCommand = config.cliCommand || 'claude-mem';
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// Fallback to default if config read fails
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function sessionEndHook() {
|
|
54
|
+
let input = '';
|
|
55
|
+
|
|
56
|
+
// Read JSON input from stdin
|
|
57
|
+
process.stdin.on('data', chunk => {
|
|
58
|
+
input += chunk;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
process.stdin.on('end', async () => {
|
|
62
|
+
try {
|
|
63
|
+
const data = JSON.parse(input);
|
|
64
|
+
const { reason, transcript_path, session_id } = data;
|
|
65
|
+
|
|
66
|
+
// Only proceed if reason is 'clear'
|
|
67
|
+
if (reason !== 'clear') {
|
|
68
|
+
// For other reasons, just continue without action
|
|
69
|
+
// @docs-ref: SessionEnd hooks should not use hookSpecificOutput
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
continue: true
|
|
72
|
+
}));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Validate transcript path
|
|
77
|
+
if (!transcript_path) {
|
|
78
|
+
// @docs-ref: SessionEnd should not block session ending
|
|
79
|
+
// Just continue even if we can't compress
|
|
80
|
+
console.log(JSON.stringify({
|
|
81
|
+
continue: true,
|
|
82
|
+
systemMessage: "Warning: No transcript path provided for compression"
|
|
83
|
+
}));
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Call the CLI compress command (same as pre-compact)
|
|
88
|
+
const compressor = spawn(cliCommand, ['compress', transcript_path], {
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let stdout = '';
|
|
93
|
+
let stderr = '';
|
|
94
|
+
|
|
95
|
+
compressor.stdout.on('data', (data) => {
|
|
96
|
+
stdout += data.toString();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
compressor.stderr.on('data', (data) => {
|
|
100
|
+
stderr += data.toString();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
compressor.on('close', (code) => {
|
|
104
|
+
if (code !== 0) {
|
|
105
|
+
// Check if it's the Claude Code executable error
|
|
106
|
+
if (stderr.includes('Claude Code executable not found')) {
|
|
107
|
+
// This is expected when running outside Claude Code context
|
|
108
|
+
// @docs-ref: SessionEnd hooks should not use hookSpecificOutput
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
continue: true,
|
|
111
|
+
suppressOutput: true
|
|
112
|
+
}));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
} else {
|
|
115
|
+
// Real error - report it but allow session to end
|
|
116
|
+
// @docs-ref: Always allow session to end, use systemMessage for errors
|
|
117
|
+
console.log(JSON.stringify({
|
|
118
|
+
continue: true,
|
|
119
|
+
systemMessage: `Memory compression failed: ${stderr}`
|
|
120
|
+
}));
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Success! Memory compressed before session clear
|
|
125
|
+
// @docs-ref: SessionEnd hooks should not use hookSpecificOutput
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
continue: true,
|
|
128
|
+
suppressOutput: true,
|
|
129
|
+
systemMessage: "Memory compressed successfully before session clear"
|
|
130
|
+
}));
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
compressor.on('error', (error) => {
|
|
136
|
+
// Report error but allow session to end
|
|
137
|
+
// @docs-ref: Always allow session to end, use systemMessage for errors
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
continue: true,
|
|
140
|
+
systemMessage: `Failed to start compression: ${error.message}`
|
|
141
|
+
}));
|
|
142
|
+
process.exit(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Report error but allow session to end
|
|
147
|
+
// @docs-ref: Always allow session to end, use systemMessage for errors
|
|
148
|
+
console.log(JSON.stringify({
|
|
149
|
+
continue: true,
|
|
150
|
+
systemMessage: `Hook error: ${error.message}`
|
|
151
|
+
}));
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
sessionEndHook();
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🔑 LOCKED by @docs-agent | Change to 🔑 to allow @docs-agent edits
|
|
5
|
+
*
|
|
6
|
+
* OFFICIAL DOCS: Claude Code SessionStart Hook v2025
|
|
7
|
+
* Last Verified: 2025-08-31
|
|
8
|
+
* @see https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
9
|
+
*
|
|
10
|
+
* SessionStart Hook Payload:
|
|
11
|
+
* {
|
|
12
|
+
* "session_id": "string",
|
|
13
|
+
* "transcript_path": "string",
|
|
14
|
+
* "hook_event_name": "SessionStart",
|
|
15
|
+
* "source": "startup" | "compact" | "vscode" | "web"
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* The 'source' field indicates how the session was initiated:
|
|
19
|
+
* - "startup": New session started normally
|
|
20
|
+
* - "compact": Session started after compaction
|
|
21
|
+
* - "vscode": Session initiated from VS Code
|
|
22
|
+
* - "web": Session initiated from web interface
|
|
23
|
+
*
|
|
24
|
+
* Valid Response Format:
|
|
25
|
+
* Success without context: { "continue": true }
|
|
26
|
+
* Success with context: {
|
|
27
|
+
* "continue": true,
|
|
28
|
+
* "hookSpecificOutput": {
|
|
29
|
+
* "hookEventName": "SessionStart",
|
|
30
|
+
* "additionalContext": "string" // Context to add to session
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* Failure: { "continue": false, "stopReason": "error message" }
|
|
34
|
+
*
|
|
35
|
+
* @docs-ref: SessionStart supports hookSpecificOutput.additionalContext
|
|
36
|
+
* @see https://docs.anthropic.com/claude-code/hooks#sessionstart
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { spawn } from 'child_process';
|
|
40
|
+
import { join, dirname, basename } from 'path';
|
|
41
|
+
import { fileURLToPath } from 'url';
|
|
42
|
+
import { readFileSync, existsSync } from 'fs';
|
|
43
|
+
|
|
44
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
|
|
46
|
+
// Load configuration to get the CLI command name
|
|
47
|
+
let cliCommand = 'claude-mem'; // Default fallback
|
|
48
|
+
const configPath = join(__dirname, 'config.json');
|
|
49
|
+
if (existsSync(configPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
52
|
+
cliCommand = config.cliCommand || 'claude-mem';
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// Fallback to default if config read fails
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function sessionStartHook() {
|
|
59
|
+
let input = '';
|
|
60
|
+
|
|
61
|
+
// Read JSON input from stdin
|
|
62
|
+
process.stdin.on('data', chunk => {
|
|
63
|
+
input += chunk;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
process.stdin.on('end', async () => {
|
|
67
|
+
try {
|
|
68
|
+
const data = JSON.parse(input);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @docs-ref: SessionStart payload includes 'source' field, not 'reason'
|
|
72
|
+
* See: https://docs.anthropic.com/en/docs/claude-code/hooks#sessionstart
|
|
73
|
+
*
|
|
74
|
+
* The 'source' field indicates the session start context:
|
|
75
|
+
* - 'startup': New session (load context)
|
|
76
|
+
* - 'compact': Session after compaction (may want to load context)
|
|
77
|
+
* - 'vscode': VS Code initiated session
|
|
78
|
+
* - 'web': Web interface initiated session
|
|
79
|
+
*
|
|
80
|
+
* Note: Based on official docs, there is no 'continue' value for source.
|
|
81
|
+
* The previous check for 'reason === continue' was incorrect.
|
|
82
|
+
* We now skip context loading only for 'compact' source, as this
|
|
83
|
+
* indicates the session is continuing after compaction.
|
|
84
|
+
*/
|
|
85
|
+
if (data.source === 'compact') { // ✅ Correctly check 'source' field per official docs
|
|
86
|
+
// @docs-ref: For compact source, skip loading context as session is continuing
|
|
87
|
+
console.log(JSON.stringify({
|
|
88
|
+
continue: true
|
|
89
|
+
// No additional context needed for compact continuation
|
|
90
|
+
}));
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Extract project name from transcript path if available
|
|
95
|
+
let projectName = null;
|
|
96
|
+
if (data.transcript_path) {
|
|
97
|
+
const dir = dirname(data.transcript_path);
|
|
98
|
+
const dirName = basename(dir);
|
|
99
|
+
|
|
100
|
+
// Extract project name from directory pattern
|
|
101
|
+
if (dirName.includes('-Scripts-')) {
|
|
102
|
+
const parts = dirName.split('-Scripts-');
|
|
103
|
+
if (parts.length > 1) {
|
|
104
|
+
projectName = parts[1];
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
projectName = dirName;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sanitize project name to match what compression uses
|
|
111
|
+
// This ensures we match the exact project name in the index
|
|
112
|
+
if (projectName) {
|
|
113
|
+
projectName = projectName.replace(/[^a-zA-Z0-9]/g, '_');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build command to run
|
|
118
|
+
const args = ['load-context', '--format', 'session-start'];
|
|
119
|
+
|
|
120
|
+
// Add project filter if we have a project name
|
|
121
|
+
if (projectName) {
|
|
122
|
+
args.push('--project', projectName);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Call the CLI load-context command directly
|
|
126
|
+
// Use the configured CLI command name (claude-mem)
|
|
127
|
+
// This ensures we use the correct version with full summaries
|
|
128
|
+
const loader = spawn(cliCommand, args, {
|
|
129
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
let stdout = '';
|
|
133
|
+
let stderr = '';
|
|
134
|
+
|
|
135
|
+
loader.stdout.on('data', (data) => {
|
|
136
|
+
stdout += data.toString();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
loader.stderr.on('data', (data) => {
|
|
140
|
+
stderr += data.toString();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
loader.on('close', (code) => {
|
|
144
|
+
if (code !== 0) {
|
|
145
|
+
// If load-context fails, just continue without context
|
|
146
|
+
// This could happen if no index exists yet
|
|
147
|
+
// @docs-ref: Always return valid response format
|
|
148
|
+
console.log(JSON.stringify({
|
|
149
|
+
continue: true
|
|
150
|
+
}));
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Output the formatted context for Claude to see using proper JSON format
|
|
155
|
+
// @docs-ref: SessionStart supports hookSpecificOutput.additionalContext
|
|
156
|
+
if (stdout && stdout.trim()) {
|
|
157
|
+
console.log(JSON.stringify({
|
|
158
|
+
continue: true,
|
|
159
|
+
hookSpecificOutput: {
|
|
160
|
+
hookEventName: "SessionStart",
|
|
161
|
+
additionalContext: stdout
|
|
162
|
+
}
|
|
163
|
+
}));
|
|
164
|
+
} else {
|
|
165
|
+
// No context to add, just continue
|
|
166
|
+
console.log(JSON.stringify({
|
|
167
|
+
continue: true
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
process.exit(0);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
loader.on('error', () => {
|
|
175
|
+
// If there's an error running the command, continue without context
|
|
176
|
+
// We don't want to break the session start
|
|
177
|
+
// @docs-ref: Always return valid response format
|
|
178
|
+
console.log(JSON.stringify({
|
|
179
|
+
continue: true
|
|
180
|
+
}));
|
|
181
|
+
process.exit(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// Any errors, just continue without additional context
|
|
186
|
+
// @docs-ref: Always return valid response format
|
|
187
|
+
console.log(JSON.stringify({
|
|
188
|
+
continue: true
|
|
189
|
+
}));
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
sessionStartHook();
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-mem",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Memory compression system for Claude Code - persist context across sessions",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"mcp",
|
|
9
|
+
"memory",
|
|
10
|
+
"compression",
|
|
11
|
+
"knowledge-graph",
|
|
12
|
+
"transcript",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"author": "Alex Newman",
|
|
16
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/thedotmack/claude-mem.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/thedotmack/claude-mem#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/thedotmack/claude-mem/issues"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"claude-mem": "claude-mem"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"claude-mem",
|
|
34
|
+
"hooks",
|
|
35
|
+
"src"
|
|
36
|
+
]
|
|
37
|
+
}
|