@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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }
@@ -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.2.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/index.js"
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
  }