@esparkman/pensieve 0.2.0 → 0.3.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 +61 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +187 -0
- package/hooks/README.md +146 -0
- package/hooks/settings.json +44 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -224,6 +224,67 @@ Summaries of work sessions for continuity.
|
|
|
224
224
|
### Open Questions
|
|
225
225
|
Unresolved blockers or questions to address.
|
|
226
226
|
|
|
227
|
+
## Hooks Integration
|
|
228
|
+
|
|
229
|
+
Pensieve includes a CLI that integrates with Claude Code's hooks system for automatic context preservation.
|
|
230
|
+
|
|
231
|
+
### Quick Setup
|
|
232
|
+
|
|
233
|
+
Copy the example hooks configuration:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
cp node_modules/@esparkman/pensieve/hooks/settings.json ~/.claude/settings.json
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Or add to your existing settings:
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"hooks": {
|
|
244
|
+
"PreCompact": [
|
|
245
|
+
{
|
|
246
|
+
"matcher": "auto",
|
|
247
|
+
"hooks": [{ "type": "command", "command": "npx @esparkman/pensieve auto-save" }]
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"matcher": "manual",
|
|
251
|
+
"hooks": [{ "type": "command", "command": "npx @esparkman/pensieve auto-save" }]
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"SessionStart": [
|
|
255
|
+
{
|
|
256
|
+
"matcher": "compact",
|
|
257
|
+
"hooks": [{ "type": "command", "command": "npx @esparkman/pensieve load-context" }]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"matcher": "resume",
|
|
261
|
+
"hooks": [{ "type": "command", "command": "npx @esparkman/pensieve load-context" }]
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### CLI Commands
|
|
269
|
+
|
|
270
|
+
| Command | Purpose |
|
|
271
|
+
|---------|---------|
|
|
272
|
+
| `pensieve auto-save` | Save session snapshot (for PreCompact hooks) |
|
|
273
|
+
| `pensieve load-context` | Output last session context to stdout |
|
|
274
|
+
| `pensieve status` | Show database location and counts |
|
|
275
|
+
|
|
276
|
+
### CLI Options
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Auto-save with custom summary
|
|
280
|
+
pensieve auto-save --summary "Completed auth feature" --wip "Testing in progress"
|
|
281
|
+
|
|
282
|
+
# Load context as JSON
|
|
283
|
+
pensieve load-context --format json
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
See [hooks/README.md](hooks/README.md) for detailed configuration options.
|
|
287
|
+
|
|
227
288
|
## Development
|
|
228
289
|
|
|
229
290
|
```bash
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { MemoryDatabase } from './database.js';
|
|
4
|
+
// Check if we should run in MCP server mode (no CLI arguments)
|
|
5
|
+
// This maintains backward compatibility with existing MCP registrations
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
if (args.length === 0) {
|
|
8
|
+
// No arguments - run MCP server
|
|
9
|
+
import('./index.js');
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
// Arguments provided - run CLI
|
|
13
|
+
runCLI();
|
|
14
|
+
}
|
|
15
|
+
async function runCLI() {
|
|
16
|
+
const program = new Command();
|
|
17
|
+
program
|
|
18
|
+
.name('pensieve')
|
|
19
|
+
.description('Persistent memory CLI for Claude Code')
|
|
20
|
+
.version('0.2.1');
|
|
21
|
+
program
|
|
22
|
+
.command('auto-save')
|
|
23
|
+
.description('Save a minimal session snapshot (for PreCompact hooks)')
|
|
24
|
+
.option('-s, --summary <text>', 'Session summary')
|
|
25
|
+
.option('-w, --wip <text>', 'Work in progress description')
|
|
26
|
+
.option('-n, --next <text>', 'Next steps')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
const db = await MemoryDatabase.create();
|
|
30
|
+
// Get or create current session
|
|
31
|
+
const session = db.getCurrentSession();
|
|
32
|
+
let sessionId;
|
|
33
|
+
if (!session) {
|
|
34
|
+
sessionId = db.startSession();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
sessionId = session.id;
|
|
38
|
+
}
|
|
39
|
+
// Build summary - use provided or generate auto-save message
|
|
40
|
+
const timestamp = new Date().toISOString();
|
|
41
|
+
const summary = options.summary || `Auto-save before compaction at ${timestamp}`;
|
|
42
|
+
const wip = options.wip || undefined;
|
|
43
|
+
const nextSteps = options.next || undefined;
|
|
44
|
+
// End the session with the summary
|
|
45
|
+
db.endSession(sessionId, summary, wip, nextSteps);
|
|
46
|
+
// Output confirmation to stderr (stdout reserved for data)
|
|
47
|
+
console.error(`[Pensieve] Session saved: ${summary}`);
|
|
48
|
+
db.close();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`[Pensieve] Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
program
|
|
57
|
+
.command('load-context')
|
|
58
|
+
.description('Output last session context to stdout (for SessionStart hooks)')
|
|
59
|
+
.option('-f, --format <type>', 'Output format: text or json', 'text')
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
try {
|
|
62
|
+
const db = await MemoryDatabase.create();
|
|
63
|
+
const lastSession = db.getLastSession();
|
|
64
|
+
const decisions = db.getRecentDecisions(10);
|
|
65
|
+
const prefs = db.getAllPreferences();
|
|
66
|
+
const questions = db.getOpenQuestions();
|
|
67
|
+
if (options.format === 'json') {
|
|
68
|
+
const context = {
|
|
69
|
+
lastSession: lastSession ? {
|
|
70
|
+
started_at: lastSession.started_at,
|
|
71
|
+
ended_at: lastSession.ended_at,
|
|
72
|
+
summary: lastSession.summary,
|
|
73
|
+
work_in_progress: lastSession.work_in_progress,
|
|
74
|
+
next_steps: lastSession.next_steps,
|
|
75
|
+
key_files: lastSession.key_files,
|
|
76
|
+
} : null,
|
|
77
|
+
decisions: decisions.map(d => ({
|
|
78
|
+
topic: d.topic,
|
|
79
|
+
decision: d.decision,
|
|
80
|
+
rationale: d.rationale,
|
|
81
|
+
})),
|
|
82
|
+
preferences: prefs.map(p => ({
|
|
83
|
+
category: p.category,
|
|
84
|
+
key: p.key,
|
|
85
|
+
value: p.value,
|
|
86
|
+
})),
|
|
87
|
+
openQuestions: questions.map(q => ({
|
|
88
|
+
id: q.id,
|
|
89
|
+
question: q.question,
|
|
90
|
+
context: q.context,
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
console.log(JSON.stringify(context, null, 2));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Text format for hook injection
|
|
97
|
+
let output = '';
|
|
98
|
+
if (lastSession?.ended_at) {
|
|
99
|
+
output += '## Previous Session Context\n\n';
|
|
100
|
+
if (lastSession.summary) {
|
|
101
|
+
output += `**Last Session:** ${lastSession.summary}\n\n`;
|
|
102
|
+
}
|
|
103
|
+
if (lastSession.work_in_progress) {
|
|
104
|
+
output += `**Work in Progress:** ${lastSession.work_in_progress}\n\n`;
|
|
105
|
+
}
|
|
106
|
+
if (lastSession.next_steps) {
|
|
107
|
+
output += `**Next Steps:** ${lastSession.next_steps}\n\n`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (decisions.length > 0) {
|
|
111
|
+
output += '## Key Decisions\n\n';
|
|
112
|
+
decisions.forEach(d => {
|
|
113
|
+
output += `- **${d.topic}:** ${d.decision}\n`;
|
|
114
|
+
});
|
|
115
|
+
output += '\n';
|
|
116
|
+
}
|
|
117
|
+
if (prefs.length > 0) {
|
|
118
|
+
output += '## Preferences\n\n';
|
|
119
|
+
prefs.forEach(p => {
|
|
120
|
+
output += `- **${p.category}/${p.key}:** ${p.value}\n`;
|
|
121
|
+
});
|
|
122
|
+
output += '\n';
|
|
123
|
+
}
|
|
124
|
+
if (questions.length > 0) {
|
|
125
|
+
output += '## Open Questions\n\n';
|
|
126
|
+
questions.forEach(q => {
|
|
127
|
+
output += `- [#${q.id}] ${q.question}\n`;
|
|
128
|
+
});
|
|
129
|
+
output += '\n';
|
|
130
|
+
}
|
|
131
|
+
if (output) {
|
|
132
|
+
console.log(output.trim());
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log('No previous context found.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
db.close();
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error(`[Pensieve] Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
program
|
|
147
|
+
.command('status')
|
|
148
|
+
.description('Show database status and counts')
|
|
149
|
+
.action(async () => {
|
|
150
|
+
try {
|
|
151
|
+
const db = await MemoryDatabase.create();
|
|
152
|
+
const decisions = db.getRecentDecisions(1000);
|
|
153
|
+
const prefs = db.getAllPreferences();
|
|
154
|
+
const entities = db.getAllEntities();
|
|
155
|
+
const questions = db.getOpenQuestions();
|
|
156
|
+
const lastSession = db.getLastSession();
|
|
157
|
+
console.log('Pensieve Status');
|
|
158
|
+
console.log('===============');
|
|
159
|
+
console.log(`Database: ${db.getPath()}`);
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log('Counts:');
|
|
162
|
+
console.log(` Decisions: ${decisions.length}`);
|
|
163
|
+
console.log(` Preferences: ${prefs.length}`);
|
|
164
|
+
console.log(` Entities: ${entities.length}`);
|
|
165
|
+
console.log(` Open Questions: ${questions.length}`);
|
|
166
|
+
console.log('');
|
|
167
|
+
if (lastSession) {
|
|
168
|
+
console.log('Last Session:');
|
|
169
|
+
console.log(` Started: ${lastSession.started_at}`);
|
|
170
|
+
console.log(` Ended: ${lastSession.ended_at || 'In progress'}`);
|
|
171
|
+
if (lastSession.summary) {
|
|
172
|
+
console.log(` Summary: ${lastSession.summary.substring(0, 80)}${lastSession.summary.length > 80 ? '...' : ''}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log('No sessions recorded yet.');
|
|
177
|
+
}
|
|
178
|
+
db.close();
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
console.error(`[Pensieve] Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
program.parse();
|
|
187
|
+
}
|
package/hooks/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Pensieve Hooks Integration
|
|
2
|
+
|
|
3
|
+
Pensieve can integrate with Claude Code's hooks system to automatically save and restore session context during compaction and session resumption.
|
|
4
|
+
|
|
5
|
+
## Quick Setup
|
|
6
|
+
|
|
7
|
+
1. Copy the example `settings.json` to your Claude Code settings directory:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# macOS/Linux
|
|
11
|
+
cp hooks/settings.json ~/.claude/settings.json
|
|
12
|
+
|
|
13
|
+
# Or merge with existing settings if you have them
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. The hooks will automatically:
|
|
17
|
+
- **PreCompact**: Save a session snapshot before context compaction
|
|
18
|
+
- **SessionStart**: Load previous context when resuming a session
|
|
19
|
+
|
|
20
|
+
## CLI Commands
|
|
21
|
+
|
|
22
|
+
Pensieve provides these CLI commands for hook integration:
|
|
23
|
+
|
|
24
|
+
### `pensieve auto-save`
|
|
25
|
+
|
|
26
|
+
Saves a minimal session snapshot. Used by PreCompact hooks.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Basic auto-save (timestamps the save automatically)
|
|
30
|
+
pensieve auto-save
|
|
31
|
+
|
|
32
|
+
# With custom summary
|
|
33
|
+
pensieve auto-save --summary "Implemented user auth"
|
|
34
|
+
|
|
35
|
+
# With work-in-progress and next steps
|
|
36
|
+
pensieve auto-save \
|
|
37
|
+
--summary "Working on auth" \
|
|
38
|
+
--wip "OAuth flow partially complete" \
|
|
39
|
+
--next "Add refresh token handling"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `pensieve load-context`
|
|
43
|
+
|
|
44
|
+
Outputs the last session context to stdout. Used by SessionStart hooks.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Text format (default) - for injection into prompts
|
|
48
|
+
pensieve load-context
|
|
49
|
+
|
|
50
|
+
# JSON format - for programmatic use
|
|
51
|
+
pensieve load-context --format json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `pensieve status`
|
|
55
|
+
|
|
56
|
+
Shows database location and counts.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pensieve status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Hook Configuration
|
|
63
|
+
|
|
64
|
+
The hooks system uses matchers to determine when to run:
|
|
65
|
+
|
|
66
|
+
### PreCompact Hooks
|
|
67
|
+
|
|
68
|
+
Run before Claude Code compacts the conversation context:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"hooks": {
|
|
73
|
+
"PreCompact": [
|
|
74
|
+
{
|
|
75
|
+
"matcher": "auto",
|
|
76
|
+
"hooks": [{ "type": "command", "command": "pensieve auto-save" }]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"matcher": "manual",
|
|
80
|
+
"hooks": [{ "type": "command", "command": "pensieve auto-save" }]
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### SessionStart Hooks
|
|
88
|
+
|
|
89
|
+
Run when starting or resuming a session:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"hooks": {
|
|
94
|
+
"SessionStart": [
|
|
95
|
+
{
|
|
96
|
+
"matcher": "compact",
|
|
97
|
+
"hooks": [{ "type": "command", "command": "pensieve load-context" }]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"matcher": "resume",
|
|
101
|
+
"hooks": [{ "type": "command", "command": "pensieve load-context" }]
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Environment Variables
|
|
109
|
+
|
|
110
|
+
You can control which database Pensieve uses:
|
|
111
|
+
|
|
112
|
+
- `PENSIEVE_DB_PATH` - Explicit path to the database file
|
|
113
|
+
- `PENSIEVE_PROJECT_DIR` - Project directory (uses `$dir/.pensieve/memory.sqlite`)
|
|
114
|
+
|
|
115
|
+
Example hook with project-specific database:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"type": "command",
|
|
120
|
+
"command": "PENSIEVE_PROJECT_DIR=$(pwd) pensieve auto-save"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
### Check if Pensieve is working
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pensieve status
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Test the hooks manually
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Test auto-save
|
|
136
|
+
pensieve auto-save --summary "Test save"
|
|
137
|
+
|
|
138
|
+
# Test load-context
|
|
139
|
+
pensieve load-context
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Database location
|
|
143
|
+
|
|
144
|
+
By default, Pensieve stores data in:
|
|
145
|
+
- Project-local: `./.pensieve/memory.sqlite` (if `.git` or `.pensieve` exists)
|
|
146
|
+
- Global fallback: `~/.claude-pensieve/memory.sqlite`
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreCompact": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "auto",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "npx @esparkman/pensieve auto-save"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "manual",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "npx @esparkman/pensieve auto-save"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"SessionStart": [
|
|
24
|
+
{
|
|
25
|
+
"matcher": "compact",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "npx @esparkman/pensieve load-context"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"matcher": "resume",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "npx @esparkman/pensieve load-context"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esparkman/pensieve",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Pensieve - persistent memory for Claude Code. Remember decisions, preferences, and context across sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"pensieve": "./dist/
|
|
8
|
+
"pensieve": "./dist/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
30
|
+
"commander": "^13.0.0",
|
|
30
31
|
"sql.js": "^1.12.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
},
|
|
42
43
|
"files": [
|
|
43
44
|
"dist",
|
|
44
|
-
".claude"
|
|
45
|
+
".claude",
|
|
46
|
+
"hooks"
|
|
45
47
|
]
|
|
46
48
|
}
|