Compare commits

..

6 Commits

Author SHA1 Message Date
Alex Newman fda069bb07 Fix GitHub issues #76, #74, #75 + session lifecycle improvements (#77)
* Refactor context-hook: Fix anti-patterns and improve maintainability

This refactor addresses all anti-patterns documented in CLAUDE.md, improving code quality without changing behavior.

**Dead Code Removed:**
- Eliminated unused useIndexView parameter throughout
- Cleaned up entry point logic

**Magic Numbers → Named Constants:**
- CHARS_PER_TOKEN_ESTIMATE = 4 (token estimation)
- SUMMARY_LOOKAHEAD = 1 (explains +1 in query)
- Added clarifying comment for DISPLAY_SESSION_COUNT

**Code Duplication Eliminated:**
- Reduced 34 lines to 4 lines with renderSummaryField() helper
- Replaced 4 identical summary field rendering blocks

**Error Handling Added:**
- parseJsonArray() now catches JSON.parse exceptions
- Prevents session crashes from malformed data

**Type Safety Improved:**
- Added SessionSummary interface (replaced inline type cast)
- Added SummaryTimelineItem for timeline items
- Proper Map typing: Map<string, TimelineItem[]>

**Variable Naming Clarity:**
- summariesWithOffset → summariesForTimeline
- isMostRecent → shouldShowLink (explains purpose)
- dayTimelines → itemsByDay
- nextSummary → olderSummary (correct chronology)

**Better Documentation:**
- Explained confusing timeline offset logic
- Removed apologetic comments, added clarifying ones

**Impact:**
- 28 lines saved from duplication elimination
- Zero behavioral changes (output identical)
- Improved maintainability and type safety

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix context-hook to respect settings.json contextDepth

The context-hook was ignoring the user's contextDepth setting from the web UI (stored in ~/.claude-mem/settings.json) and always using the default of 50 observations.

**Problem:**
- Web UI sets contextDepth in ~/.claude-mem/settings.json
- Context-hook only read from process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS
- User's preference of 7 observations was ignored, always showing 50

**Solution:**
- Added getContextDepth() function following same pattern as getWorkerPort()
- Priority: settings.json > env var > default (50)
- Validates contextDepth is a positive number

**Testing:**
- Verified with contextDepth: 7 → shows 7 observations ✓
- Verified with contextDepth: 3 → shows 3 observations ✓
- Settings properly respected on every session start

**Files Changed:**
- src/hooks/context-hook.ts: Added getContextDepth() + imports
- plugin/scripts/context-hook.js: Built output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix context-hook to read from correct settings file location

**Critical Bug Fix:**
Previous commit read from completely wrong location with wrong field name.

**What was wrong:**
- Reading from: ~/.claude-mem/settings.json (doesn't exist)
- Looking for: contextDepth (wrong field)
- Result: Always falling back to default of 50

**What's fixed:**
- Reading from: ~/.claude/settings.json (correct location)
- Looking for: env.CLAUDE_MEM_CONTEXT_OBSERVATIONS (correct field)
- Matches pattern used in worker-service.ts

**Testing:**
- With CLAUDE_MEM_CONTEXT_OBSERVATIONS: "15" → shows 15 observations ✓
- With CLAUDE_MEM_CONTEXT_OBSERVATIONS: "5" → shows 5 observations ✓
- Web UI settings now properly respected

**Files Changed:**
- src/hooks/context-hook.ts: Fixed path and field name in getContextDepth()
- plugin/scripts/context-hook.js: Built output

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix GitHub issues #76, #74, #75 + session lifecycle improvements

Bug Fixes:
- Fix PM2 'Process 0 not found' error (#76): Changed pm2 restart to pm2 start (idempotent)
- Fix troubleshooting skill distribution (#74, #75): Moved from .claude/skills/ to plugin/skills/

Session Lifecycle Improvements:
- Added session lifecycle context to SDK agent prompt
- Changed summary framing from "final report" to "progress checkpoint"
- Updated summary prompts to use progressive tense ("so far", "actively working on")
- Added buildContinuationPrompt() for prompt #2+ to avoid re-initialization
- SessionManager now restores prompt counter from database
- SDKAgent conditionally uses init vs continuation prompt based on prompt number

These changes improve context-loading task handling and reduce incorrect "file not found" reports in summaries (partial fix for #73 - awaiting user feedback).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Release v5.3.0: Session lifecycle improvements and bug fixes

Improvements:
- Session prompt counter now restored from DB on worker restart
- Continuation prompts for prompt #2+ (lightweight, avoids re-init)
- Summary framing changed from "final report" to "progress checkpoint"
- PM2 start command (idempotent, fixes "Process 0 not found" error)
- Troubleshooting skill moved to plugin/skills/ for proper distribution

Technical changes:
- SessionManager loads prompt_counter from DB on initialization
- SDKAgent uses buildContinuationPrompt() for requests #2+
- Updated summary prompt to clarify mid-session checkpoints
- Fixed worker-utils.ts to use pm2 start instead of pm2 restart
- Moved .claude/skills/troubleshoot → plugin/skills/troubleshoot

Fixes:
- GitHub issue #76: PM2 "Process 0 not found" error
- GitHub issue #74, #75: Troubleshooting skill not distributed
- GitHub issue #73 (partial): Context-loading tasks reported as failed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 16:44:58 -05:00
Alex Newman 4c1f7b4ff1 Release v5.2.3: Added troubleshooting skill
Improvements:
- Added troubleshooting slash command skill for diagnosing claude-mem installation issues
- Comprehensive diagnostic workflow covering PM2, worker health, database, dependencies, logs, and viewer UI
- Automated fix sequences and common issue resolutions
- Full system diagnostic report generation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 19:29:26 -05:00
claude[bot] 35b98c53f0 Remove unnecessary troubleshooting checks from skill
Simplified troubleshooting diagnostics by removing checks that are:
- Too brittle (300+ packages requirement)
- Not relevant to v5.2.2 issues (version cache)
- Redundant (Node.js version checks)

Changes:
- Removed "300+ packages installed" expectation
- Removed entire "Step 5: Check Version Cache" section
- Removed dependency count check (ls node_modules/ | wc -l)
- Removed Node.js version checks from multiple sections
- Renumbered steps from 8 to 7

Result: More focused diagnostics for reported issues (memory persistence,
viewer state, observation freshness) without false positives.

Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com>
2025-11-08 23:21:52 +00:00
copilot-swe-agent[bot] e029903a66 Add troubleshooting slash command skill
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-08 21:47:03 +00:00
copilot-swe-agent[bot] 5666f7dd1c Initial exploration and planning for troubleshooting slash command
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
2025-11-08 21:43:06 +00:00
copilot-swe-agent[bot] 57f0c9fd92 Initial plan 2025-11-08 21:39:44 +00:00
18 changed files with 593 additions and 169 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "5.2.2",
"version": "5.3.0",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+1 -1
View File
@@ -6,7 +6,7 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
**Your Role**: You are working on the plugin itself. When users interact with Claude Code with this plugin installed, your observations get captured and become their persistent memory.
**Current Version**: 5.2.2
**Current Version**: 5.3.0
## Critical Architecture Knowledge
+8
View File
@@ -305,6 +305,14 @@ See [Development Guide](docs/development.mdx) for detailed instructions.
## Troubleshooting
**Quick Diagnostic:**
Run the troubleshooting skill for automated diagnosis and fixes:
```
/skill troubleshoot
```
**Common Issues:**
- Worker not starting → `npm run worker:restart`
+20
View File
@@ -5,6 +5,26 @@ description: "Common issues and solutions for Claude-Mem"
# Troubleshooting Guide
## Quick Diagnostic Tool
**NEW:** Use the automated troubleshooting skill for instant diagnosis:
```
/skill troubleshoot
```
This skill will:
- ✅ Check PM2 worker status and health
- ✅ Verify database existence and integrity
- ✅ Test worker service connectivity
- ✅ Validate dependencies installation
- ✅ Check port configuration and availability
- ✅ Provide automated fixes for common issues
The skill includes comprehensive diagnostics, automated repair sequences, and detailed troubleshooting workflows for all common issues. Use it before manually troubleshooting below.
---
## v5.x Specific Issues
### Viewer UI Not Loading
+2 -6
View File
@@ -1,12 +1,12 @@
{
"name": "claude-mem",
"version": "5.1.4",
"version": "5.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-mem",
"version": "5.1.4",
"version": "5.2.2",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.27",
@@ -1484,7 +1484,6 @@
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2338,7 +2337,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -3851,7 +3849,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -4853,7 +4850,6 @@
"node_modules/zod": {
"version": "3.25.76",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "5.2.2",
"version": "5.3.0",
"description": "Memory compression system for Claude Code - persist context across sessions",
"keywords": [
"claude",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "5.2.2",
"version": "5.3.0",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+31 -31
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import X from"path";import{stdin as w}from"process";import se from"better-sqlite3";import{join as f,dirname as Q,basename as _e}from"path";import{homedir as j}from"os";import{existsSync as Ee,mkdirSync as z}from"fs";import{fileURLToPath as Z}from"url";function ee(){return typeof __dirname<"u"?__dirname:Q(Z(import.meta.url))}var ge=ee(),I=process.env.CLAUDE_MEM_DATA_DIR||f(j(),".claude-mem"),$=process.env.CLAUDE_CONFIG_DIR||f(j(),".claude"),he=f(I,"archives"),be=f(I,"logs"),Se=f(I,"trash"),fe=f(I,"backups"),Re=f(I,"settings.json"),P=f(I,"claude-mem.db"),Ne=f(I,"vector-db"),Oe=f($,"settings.json"),Ie=f($,"commands"),Le=f($,"CLAUDE.md");function H(p){z(p,{recursive:!0})}var U=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(U||{}),M=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=U[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),a=U[e].padEnd(5),_=s.padEnd(6),T="";r?.correlationId?T=`[${r.correlationId}] `:r?.sessionId&&(T=`[session-${r.sessionId}] `);let S="";i!=null&&(this.level===0&&typeof i=="object"?S=`
`+JSON.stringify(i,null,2):S=" "+this.formatData(i));let n="";if(r){let{sessionId:R,sdkSessionId:N,correlationId:l,...c}=r;Object.keys(c).length>0&&(n=` {${Object.entries(c).map(([u,g])=>`${u}=${g}`).join(", ")}}`)}let v=`[${d}] [${a}] [${_}] ${T}${t}${n}${S}`;e===3?console.error(v):console.log(v)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},G=new M;var D=class{db;constructor(){H(I),this.db=new se(P),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
import x from"path";import{homedir as se}from"os";import{existsSync as te,readFileSync as re}from"fs";import{stdin as F}from"process";import ee from"better-sqlite3";import{join as b,dirname as J,basename as Te}from"path";import{homedir as j}from"os";import{existsSync as Se,mkdirSync as Q}from"fs";import{fileURLToPath as z}from"url";function Z(){return typeof __dirname<"u"?__dirname:J(z(import.meta.url))}var fe=Z(),N=process.env.CLAUDE_MEM_DATA_DIR||b(j(),".claude-mem"),$=process.env.CLAUDE_CONFIG_DIR||b(j(),".claude"),Ne=b(N,"archives"),Oe=b(N,"logs"),Ie=b(N,"trash"),Le=b(N,"backups"),ye=b(N,"settings.json"),P=b(N,"claude-mem.db"),Ae=b(N,"vector-db"),ve=b($,"settings.json"),Ce=b($,"commands"),De=b($,"CLAUDE.md");function H(c){Q(c,{recursive:!0})}var M=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(M||{}),w=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=M[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,i){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),p=M[e].padEnd(5),_=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let n="";i!=null&&(this.level===0&&typeof i=="object"?n=`
`+JSON.stringify(i,null,2):n=" "+this.formatData(i));let O="";if(r){let{sessionId:S,sdkSessionId:R,correlationId:l,...a}=r;Object.keys(a).length>0&&(O=` {${Object.entries(a).map(([T,m])=>`${T}=${m}`).join(", ")}}`)}let L=`[${d}] [${p}] [${_}] ${E}${t}${O}${n}`;e===3?console.error(L):console.log(L)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},G=new w;var C=class{db;constructor(){H(N),this.db=new ee(P),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable(),this.createUserPromptsTable()}initializeSchema(){try{this.db.exec(`
CREATE TABLE IF NOT EXISTS schema_versions (
id INTEGER PRIMARY KEY,
version INTEGER UNIQUE NOT NULL,
@@ -244,10 +244,10 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT *
FROM observations
WHERE id = ?
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",a=e.map(()=>"?").join(",");return this.db.prepare(`
`).get(e)||null}getObservationsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",p=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT *
FROM observations
WHERE id IN (${a})
WHERE id IN (${p})
ORDER BY created_at_epoch ${i}
${d}
`).all(...e)}getSummaryForSession(e){return this.db.prepare(`
@@ -262,7 +262,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT files_read, files_modified
FROM observations
WHERE sdk_session_id = ?
`).all(e),r=new Set,i=new Set;for(let d of t){if(d.files_read)try{let a=JSON.parse(d.files_read);Array.isArray(a)&&a.forEach(_=>r.add(_))}catch{}if(d.files_modified)try{let a=JSON.parse(d.files_modified);Array.isArray(a)&&a.forEach(_=>i.add(_))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(i)}}getSessionById(e){return this.db.prepare(`
`).all(e),r=new Set,i=new Set;for(let d of t){if(d.files_read)try{let p=JSON.parse(d.files_read);Array.isArray(p)&&p.forEach(_=>r.add(_))}catch{}if(d.files_modified)try{let p=JSON.parse(d.files_modified);Array.isArray(p)&&p.forEach(_=>i.add(_))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(i)}}getSessionById(e){return this.db.prepare(`
SELECT id, claude_session_id, sdk_session_id, project, user_prompt
FROM sdk_sessions
WHERE id = ?
@@ -289,13 +289,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,i=r.getTime(),a=this.db.prepare(`
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,i=r.getTime(),p=this.db.prepare(`
INSERT OR IGNORE INTO sdk_sessions
(claude_session_id, sdk_session_id, project, user_prompt, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, ?, 'active')
`).run(e,e,s,t,r.toISOString(),i);return a.lastInsertRowid===0||a.changes===0?this.db.prepare(`
`).run(e,e,s,t,r.toISOString(),i);return p.lastInsertRowid===0||p.changes===0?this.db.prepare(`
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
`).get(e).id:a.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
`).get(e).id:p.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
UPDATE sdk_sessions
SET sdk_session_id = ?
WHERE id = ? AND sdk_session_id IS NULL
@@ -318,23 +318,23 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let S=this.db.prepare(`
`).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let n=this.db.prepare(`
INSERT INTO observations
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
files_read, files_modified, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,i.toISOString(),d);return{id:Number(S.lastInsertRowid),createdAtEpoch:d}}storeSummary(e,s,t,r){let i=new Date,d=i.getTime();this.db.prepare(`
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,i.toISOString(),d);return{id:Number(n.lastInsertRowid),createdAtEpoch:d}}storeSummary(e,s,t,r){let i=new Date,d=i.getTime();this.db.prepare(`
SELECT id FROM sdk_sessions WHERE sdk_session_id = ?
`).get(e)||(this.db.prepare(`
INSERT INTO sdk_sessions
(claude_session_id, sdk_session_id, project, started_at, started_at_epoch, status)
VALUES (?, ?, ?, ?, ?, 'active')
`).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let S=this.db.prepare(`
`).run(e,e,s,i.toISOString(),d),console.error(`[SessionStore] Auto-created session record for session_id: ${e}`));let n=this.db.prepare(`
INSERT INTO session_summaries
(sdk_session_id, project, request, investigated, learned, completed,
next_steps, notes, prompt_number, created_at, created_at_epoch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,i.toISOString(),d);return{id:Number(S.lastInsertRowid),createdAtEpoch:d}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,i.toISOString(),d);return{id:Number(n.lastInsertRowid),createdAtEpoch:d}}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
UPDATE sdk_sessions
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
@@ -342,62 +342,62 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
UPDATE sdk_sessions
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
WHERE id = ?
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",a=e.map(()=>"?").join(",");return this.db.prepare(`
`).run(s.toISOString(),t,e)}getSessionSummariesByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",p=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT * FROM session_summaries
WHERE id IN (${a})
WHERE id IN (${p})
ORDER BY created_at_epoch ${i}
${d}
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",a=e.map(()=>"?").join(",");return this.db.prepare(`
`).all(...e)}getUserPromptsByIds(e,s={}){if(e.length===0)return[];let{orderBy:t="date_desc",limit:r}=s,i=t==="date_asc"?"ASC":"DESC",d=r?`LIMIT ${r}`:"",p=e.map(()=>"?").join(",");return this.db.prepare(`
SELECT
up.*,
s.project,
s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.id IN (${a})
WHERE up.id IN (${p})
ORDER BY up.created_at_epoch ${i}
${d}
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,i){let d=i?"AND project = ?":"",a=i?[i]:[],_,T;if(e!==null){let R=`
`).all(...e)}getTimelineAroundTimestamp(e,s=10,t=10,r){return this.getTimelineAroundObservation(null,e,s,t,r)}getTimelineAroundObservation(e,s,t=10,r=10,i){let d=i?"AND project = ?":"",p=i?[i]:[],_,E;if(e!==null){let S=`
SELECT id, created_at_epoch
FROM observations
WHERE id <= ? ${d}
ORDER BY id DESC
LIMIT ?
`,N=`
`,R=`
SELECT id, created_at_epoch
FROM observations
WHERE id >= ? ${d}
ORDER BY id ASC
LIMIT ?
`;try{let l=this.db.prepare(R).all(e,...a,t+1),c=this.db.prepare(N).all(e,...a,r+1);if(l.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,T=c.length>0?c[c.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary observations:",l.message),{observations:[],sessions:[],prompts:[]}}}else{let R=`
`;try{let l=this.db.prepare(S).all(e,...p,t+1),a=this.db.prepare(R).all(e,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary observations:",l.message),{observations:[],sessions:[],prompts:[]}}}else{let S=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch <= ? ${d}
ORDER BY created_at_epoch DESC
LIMIT ?
`,N=`
`,R=`
SELECT created_at_epoch
FROM observations
WHERE created_at_epoch >= ? ${d}
ORDER BY created_at_epoch ASC
LIMIT ?
`;try{let l=this.db.prepare(R).all(s,...a,t),c=this.db.prepare(N).all(s,...a,r+1);if(l.length===0&&c.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,T=c.length>0?c[c.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary timestamps:",l.message),{observations:[],sessions:[],prompts:[]}}}let S=`
`;try{let l=this.db.prepare(S).all(s,...p,t),a=this.db.prepare(R).all(s,...p,r+1);if(l.length===0&&a.length===0)return{observations:[],sessions:[],prompts:[]};_=l.length>0?l[l.length-1].created_at_epoch:s,E=a.length>0?a[a.length-1].created_at_epoch:s}catch(l){return console.error("[SessionStore] Error getting boundary timestamps:",l.message),{observations:[],sessions:[],prompts:[]}}}let n=`
SELECT *
FROM observations
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${d}
ORDER BY created_at_epoch ASC
`,n=`
`,O=`
SELECT *
FROM session_summaries
WHERE created_at_epoch >= ? AND created_at_epoch <= ? ${d}
ORDER BY created_at_epoch ASC
`,v=`
`,L=`
SELECT up.*, s.project, s.sdk_session_id
FROM user_prompts up
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${d.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let R=this.db.prepare(S).all(_,T,...a),N=this.db.prepare(n).all(_,T,...a),l=this.db.prepare(v).all(_,T,...a);return{observations:R,sessions:N.map(c=>({id:c.id,sdk_session_id:c.sdk_session_id,project:c.project,request:c.request,completed:c.completed,next_steps:c.next_steps,created_at:c.created_at,created_at_epoch:c.created_at_epoch})),prompts:l.map(c=>({id:c.id,claude_session_id:c.claude_session_id,project:c.project,prompt:c.prompt_text,created_at:c.created_at,created_at_epoch:c.created_at_epoch}))}}catch(R){return console.error("[SessionStore] Error querying timeline records:",R.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};var te=parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10),W=10,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function re(p){if(!p)return[];let e=JSON.parse(p);return Array.isArray(e)?e:[]}function ne(p){return new Date(p).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function ie(p){return new Date(p).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function oe(p){return new Date(p).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function ae(p){return p?Math.ceil(p.length/4):0}function de(p,e){return X.isAbsolute(p)?X.relative(e,p):p}async function Y(p,e=!1,s=!1){let t=p?.cwd??process.cwd(),r=t?X.basename(t):"unknown-project",i=new D,d=i.db.prepare(`
`;try{let S=this.db.prepare(n).all(_,E,...p),R=this.db.prepare(O).all(_,E,...p),l=this.db.prepare(L).all(_,E,...p);return{observations:S,sessions:R.map(a=>({id:a.id,sdk_session_id:a.sdk_session_id,project:a.project,request:a.request,completed:a.completed,next_steps:a.next_steps,created_at:a.created_at,created_at_epoch:a.created_at_epoch})),prompts:l.map(a=>({id:a.id,claude_session_id:a.claude_session_id,project:a.project,prompt:a.prompt_text,created_at:a.created_at,created_at_epoch:a.created_at_epoch}))}}catch(S){return console.error("[SessionStore] Error querying timeline records:",S.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function ne(){try{let c=x.join(se(),".claude","settings.json");if(te(c)){let e=JSON.parse(re(c,"utf-8"));if(e.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS){let s=parseInt(e.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS,10);if(!isNaN(s)&&s>0)return s}}}catch{}return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS||"50",10)}var ie=ne(),W=10,oe=4,ae=1,o={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m",red:"\x1B[31m"};function de(c){if(!c)return[];try{let e=JSON.parse(c);return Array.isArray(e)?e:[]}catch{return[]}}function ce(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function pe(c){return new Date(c).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}function _e(c){return new Date(c).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})}function ue(c){return c?Math.ceil(c.length/oe):0}function le(c,e){return x.isAbsolute(c)?x.relative(e,c):c}function D(c,e,s,t){return e?t?[`${s}${c}:${o.reset} ${e}`,""]:[`**${c}**: ${e}`,""]:[]}async function Y(c,e=!1){let s=c?.cwd??process.cwd(),t=s?x.basename(s):"unknown-project",r=new C,i=r.db.prepare(`
SELECT
id, sdk_session_id, type, title, subtitle, narrative,
facts, concepts, files_read, files_modified,
@@ -406,18 +406,18 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`).all(r,te),a=i.db.prepare(`
`).all(t,ie),d=r.db.prepare(`
SELECT id, sdk_session_id, request, investigated, learned, completed, next_steps, created_at, created_at_epoch
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`).all(r,W+1);if(d.length===0&&a.length===0)return i.close(),e?`
${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}
`).all(t,W+ae);if(i.length===0&&d.length===0)return r.close(),e?`
${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}
${o.gray}${"\u2500".repeat(60)}${o.reset}
${o.dim}No previous sessions found for this project yet.${o.reset}
`:`# [${r}] recent context
`:`# [${t}] recent context
No previous sessions found for this project yet.`;let _=d,T=a.slice(0,W),S=_,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${r}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${r}] recent context`),n.push("")),S.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let v=a[0]?.id,R=T.map((u,g)=>{let E=g===0?null:a[g+1];return{...u,displayEpoch:E?E.created_at_epoch:u.created_at_epoch,displayTime:E?E.created_at:u.created_at,isMostRecent:u.id===v}}),N=[...S.map(u=>({type:"observation",data:u})),...R.map(u=>({type:"summary",data:u}))];N.sort((u,g)=>{let E=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,L=g.type==="observation"?g.data.created_at_epoch:g.data.displayEpoch;return E-L});let l=new Map;for(let u of N){let g=u.type==="observation"?u.data.created_at:u.data.displayTime,E=oe(g);l.has(E)||l.set(E,[]),l.get(E).push(u)}let c=Array.from(l.entries()).sort((u,g)=>{let E=new Date(u[0]).getTime(),L=new Date(g[0]).getTime();return E-L});for(let[u,g]of c){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let E=null,L="",A=!1;for(let x of g)if(x.type==="summary"){A&&(n.push(""),A=!1,E=null,L="");let h=x.data,y=`${h.request||"Session started"} (${ne(h.displayTime)})`,O=h.isMostRecent?"":`claude-mem://session-summary/${h.id}`;if(e){let b=O?`${o.dim}[${O}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${h.id}${o.reset} ${y} ${b}`)}else{let b=O?` [\u2192](${O})`:"";n.push(`**\u{1F3AF} #S${h.id}** ${y}${b}`)}n.push("")}else{let h=x.data,y=re(h.files_modified),O=y.length>0?de(y[0],t):"General";O!==E&&(A&&n.push(""),e?n.push(`${o.dim}${O}${o.reset}`):n.push(`**${O}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),E=O,A=!0,L="");let b="\u2022";switch(h.type){case"bugfix":b="\u{1F534}";break;case"feature":b="\u{1F7E3}";break;case"refactor":b="\u{1F504}";break;case"change":b="\u2705";break;case"discovery":b="\u{1F535}";break;case"decision":b="\u{1F9E0}";break;default:b="\u2022"}let C=ie(h.created_at),F=h.title||"Untitled",k=ae(h.narrative),B=C!==L,q=B?C:"";if(L=C,e){let K=B?`${o.dim}${C}${o.reset}`:" ".repeat(C.length),J=k>0?`${o.dim}(~${k}t)${o.reset}`:"";n.push(` ${o.dim}#${h.id}${o.reset} ${K} ${b} ${F} ${J}`)}else n.push(`| #${h.id} | ${q||"\u2033"} | ${b} | ${F} | ~${k} |`)}A&&n.push("")}let m=a[0];m&&(m.investigated||m.learned||m.completed||m.next_steps)&&(m.investigated&&(e?n.push(`${o.blue}Investigated:${o.reset} ${m.investigated}`):n.push(`**Investigated**: ${m.investigated}`),n.push("")),m.learned&&(e?n.push(`${o.yellow}Learned:${o.reset} ${m.learned}`):n.push(`**Learned**: ${m.learned}`),n.push("")),m.completed&&(e?n.push(`${o.green}Completed:${o.reset} ${m.completed}`):n.push(`**Completed**: ${m.completed}`),n.push("")),m.next_steps&&(e?n.push(`${o.magenta}Next Steps:${o.reset} ${m.next_steps}`):n.push(`**Next Steps**: ${m.next_steps}`),n.push(""))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return i.close(),n.join(`
`).trimEnd()}var V=process.argv.includes("--index"),ce=process.argv.includes("--colors");if(w.isTTY||ce)Y(void 0,!0,V).then(p=>{console.log(p),process.exit(0)});else{let p="";w.on("data",e=>p+=e),w.on("end",async()=>{let e=p.trim()?JSON.parse(p):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await Y(e,!1,V)}};console.log(JSON.stringify(t)),process.exit(0)})}
No previous sessions found for this project yet.`;let p=i,_=d.slice(0,W),E=p,n=[];if(e?(n.push(""),n.push(`${o.bright}${o.cyan}\u{1F4DD} [${t}] recent context${o.reset}`),n.push(`${o.gray}${"\u2500".repeat(60)}${o.reset}`),n.push("")):(n.push(`# [${t}] recent context`),n.push("")),E.length>0){e?(n.push(`${o.dim}Legend: \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision${o.reset}`),n.push("")):(n.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),n.push("")),e?(n.push(`${o.dim}\u{1F4A1} Progressive Disclosure: This index shows WHAT exists (titles) and retrieval COST (token counts).${o.reset}`),n.push(`${o.dim} \u2192 Use MCP search tools to fetch full observation details on-demand (Layer 2)${o.reset}`),n.push(`${o.dim} \u2192 Prefer searching observations over re-reading code for past decisions and learnings${o.reset}`),n.push(`${o.dim} \u2192 Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately${o.reset}`),n.push("")):(n.push("\u{1F4A1} **Progressive Disclosure:** This index shows WHAT exists (titles) and retrieval COST (token counts)."),n.push("- Use MCP search tools to fetch full observation details on-demand (Layer 2)"),n.push("- Prefer searching observations over re-reading code for past decisions and learnings"),n.push("- Critical types (\u{1F534} bugfix, \u{1F9E0} decision) often worth fetching immediately"),n.push(""));let O=d[0]?.id,L=_.map((u,T)=>{let m=T===0?null:d[T+1];return{...u,displayEpoch:m?m.created_at_epoch:u.created_at_epoch,displayTime:m?m.created_at:u.created_at,shouldShowLink:u.id!==O}}),S=[...E.map(u=>({type:"observation",data:u})),...L.map(u=>({type:"summary",data:u}))];S.sort((u,T)=>{let m=u.type==="observation"?u.data.created_at_epoch:u.data.displayEpoch,I=T.type==="observation"?T.data.created_at_epoch:T.data.displayEpoch;return m-I});let R=new Map;for(let u of S){let T=u.type==="observation"?u.data.created_at:u.data.displayTime,m=_e(T);R.has(m)||R.set(m,[]),R.get(m).push(u)}let l=Array.from(R.entries()).sort((u,T)=>{let m=new Date(u[0]).getTime(),I=new Date(T[0]).getTime();return m-I});for(let[u,T]of l){e?(n.push(`${o.bright}${o.cyan}${u}${o.reset}`),n.push("")):(n.push(`### ${u}`),n.push(""));let m=null,I="",y=!1;for(let k of T)if(k.type==="summary"){y&&(n.push(""),y=!1,m=null,I="");let g=k.data,A=`${g.request||"Session started"} (${ce(g.displayTime)})`,f=g.shouldShowLink?`claude-mem://session-summary/${g.id}`:"";if(e){let h=f?`${o.dim}[${f}]${o.reset}`:"";n.push(`\u{1F3AF} ${o.yellow}#S${g.id}${o.reset} ${A} ${h}`)}else{let h=f?` [\u2192](${f})`:"";n.push(`**\u{1F3AF} #S${g.id}** ${A}${h}`)}n.push("")}else{let g=k.data,A=de(g.files_modified),f=A.length>0?le(A[0],s):"General";f!==m&&(y&&n.push(""),e?n.push(`${o.dim}${f}${o.reset}`):n.push(`**${f}**`),e||(n.push("| ID | Time | T | Title | Tokens |"),n.push("|----|------|---|-------|--------|")),m=f,y=!0,I="");let h="\u2022";switch(g.type){case"bugfix":h="\u{1F534}";break;case"feature":h="\u{1F7E3}";break;case"refactor":h="\u{1F504}";break;case"change":h="\u2705";break;case"discovery":h="\u{1F535}";break;case"decision":h="\u{1F9E0}";break;default:h="\u2022"}let v=pe(g.created_at),X=g.title||"Untitled",U=ue(g.narrative),B=v!==I,V=B?v:"";if(I=v,e){let q=B?`${o.dim}${v}${o.reset}`:" ".repeat(v.length),K=U>0?`${o.dim}(~${U}t)${o.reset}`:"";n.push(` ${o.dim}#${g.id}${o.reset} ${q} ${h} ${X} ${K}`)}else n.push(`| #${g.id} | ${V||"\u2033"} | ${h} | ${X} | ~${U} |`)}y&&n.push("")}let a=d[0];a&&(a.investigated||a.learned||a.completed||a.next_steps)&&(n.push(...D("Investigated",a.investigated,o.blue,e)),n.push(...D("Learned",a.learned,o.yellow,e)),n.push(...D("Completed",a.completed,o.green,e)),n.push(...D("Next Steps",a.next_steps,o.magenta,e))),e?n.push(`${o.dim}Use claude-mem MCP search to access records with the given ID${o.reset}`):n.push("*Use claude-mem MCP search to access records with the given ID*")}return r.close(),n.join(`
`).trimEnd()}var me=process.argv.includes("--colors");if(F.isTTY||me)Y(void 0,!0).then(c=>{console.log(c),process.exit(0)});else{let c="";F.on("data",e=>c+=e),F.on("end",async()=>{let e=c.trim()?JSON.parse(c):void 0,t={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:await Y(e,!1)}};console.log(JSON.stringify(t)),process.exit(0)})}
+1 -1
View File
@@ -397,4 +397,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let l=this.db.prepare(E).all(d,c,...i),b=this.db.prepare(T).all(d,c,...i),_=this.db.prepare(S).all(d,c,...i);return{observations:l,sessions:b.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:_.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import O from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function f(){try{let a=O.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=f();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=v(),e=O.join(a,"node_modules",".bin","pm2"),s=O.join(a,"ecosystem.config.cjs");if(K(`"${e}" restart "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function Z(a){if(!a)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=a,r=z.basename(s);await x();let n=new g,o=n.createSDKSession(e,r,t),i=n.incrementPromptCounter(o);n.saveUserPrompt(e,i,t),console.error(`[new-hook] Session ${o}, prompt #${i}`),n.close();let d=f();try{let c=await fetch(`http://127.0.0.1:${d}/sessions/${o}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let E=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${E}`)}}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(D("UserPromptSubmit",!0))}var I="";U.on("data",a=>I+=a);U.on("end",async()=>{let a=I?JSON.parse(I):void 0;await Z(a)});
`;try{let l=this.db.prepare(E).all(d,c,...i),b=this.db.prepare(T).all(d,c,...i),_=this.db.prepare(S).all(d,c,...i);return{observations:l,sessions:b.map(p=>({id:p.id,sdk_session_id:p.sdk_session_id,project:p.project,request:p.request,completed:p.completed,next_steps:p.next_steps,created_at:p.created_at,created_at_epoch:p.created_at_epoch})),prompts:_.map(p=>({id:p.id,claude_session_id:p.claude_session_id,project:p.project,prompt:p.prompt_text,created_at:p.created_at,created_at_epoch:p.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import O from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function f(){try{let a=O.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=f();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=v(),e=O.join(a,"node_modules",".bin","pm2"),s=O.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function Z(a){if(!a)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=a,r=z.basename(s);await x();let n=new g,o=n.createSDKSession(e,r,t),i=n.incrementPromptCounter(o);n.saveUserPrompt(e,i,t),console.error(`[new-hook] Session ${o}, prompt #${i}`),n.close();let d=f();try{let c=await fetch(`http://127.0.0.1:${d}/sessions/${o}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let E=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${E}`)}}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(D("UserPromptSubmit",!0))}var I="";U.on("data",a=>I+=a);U.on("end",async()=>{let a=I?JSON.parse(I):void 0;await Z(a)});
+1 -1
View File
@@ -397,4 +397,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${n.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let T=this.db.prepare(c).all(p,u,...i),S=this.db.prepare(m).all(p,u,...i),_=this.db.prepare(g).all(p,u,...i);return{observations:T,sessions:S.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:_.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import I from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function L(){try{let a=I.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=L();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=D(),e=I.join(a,"node_modules",".bin","pm2"),s=I.join(a,"ecosystem.config.cjs");if(K(`"${e}" restart "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}var z=new Set(["ListMcpResourcesTool"]);async function Z(a){if(!a)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_response:r}=a;if(z.has(s)){console.log(f("PostToolUse",!0));return}await x();let o=new R,n=o.createSDKSession(e,"",""),i=o.getPromptCounter(n);o.close();let p=b.formatTool(s,t),u=L();b.dataIn("HOOK",`PostToolUse: ${p}`,{sessionId:n,workerPort:u});try{let c=await fetch(`http://127.0.0.1:${u}/sessions/${n}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_response:r!==void 0?JSON.stringify(r):"{}",prompt_number:i}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw b.failure("HOOK","Failed to send observation",{sessionId:n,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}b.debug("HOOK","Observation sent successfully",{sessionId:n,toolName:s})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var A="";U.on("data",a=>A+=a);U.on("end",async()=>{let a=A?JSON.parse(A):void 0;await Z(a)});
`;try{let T=this.db.prepare(c).all(p,u,...i),S=this.db.prepare(m).all(p,u,...i),_=this.db.prepare(g).all(p,u,...i);return{observations:T,sessions:S.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:_.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(T){return console.error("[SessionStore] Error querying timeline records:",T.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function f(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import I from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var V=100,q=100,J=1e4;function L(){try{let a=I.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=L();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(V)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,q))}return!1}async function x(){if(await k())return;let a=D(),e=I.join(a,"node_modules",".bin","pm2"),s=I.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}var z=new Set(["ListMcpResourcesTool"]);async function Z(a){if(!a)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_response:r}=a;if(z.has(s)){console.log(f("PostToolUse",!0));return}await x();let o=new R,n=o.createSDKSession(e,"",""),i=o.getPromptCounter(n);o.close();let p=b.formatTool(s,t),u=L();b.dataIn("HOOK",`PostToolUse: ${p}`,{sessionId:n,workerPort:u});try{let c=await fetch(`http://127.0.0.1:${u}/sessions/${n}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_response:r!==void 0?JSON.stringify(r):"{}",prompt_number:i}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw b.failure("HOOK","Failed to send observation",{sessionId:n,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}b.debug("HOOK","Observation sent successfully",{sessionId:n,toolName:s})}catch(c){throw c.cause?.code==="ECONNREFUSED"||c.name==="TimeoutError"||c.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):c}console.log(f("PostToolUse",!0))}var A="";U.on("data",a=>A+=a);U.on("end",async()=>{let a=A?JSON.parse(A):void 0;await Z(a)});
+1 -1
View File
@@ -397,4 +397,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
JOIN sdk_sessions s ON up.claude_session_id = s.claude_session_id
WHERE up.created_at_epoch >= ? AND up.created_at_epoch <= ? ${o.replace("project","s.project")}
ORDER BY up.created_at_epoch ASC
`;try{let l=this.db.prepare(m).all(p,u,...i),b=this.db.prepare(T).all(p,u,...i),c=this.db.prepare(g).all(p,u,...i);return{observations:l,sessions:b.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:c.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import f from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var q=100,V=100,J=1e4;function I(){try{let a=f.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=I();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(q)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,V))}return!1}async function x(){if(await k())return;let a=v(),e=f.join(a,"node_modules",".bin","pm2"),s=f.join(a,"ecosystem.config.cjs");if(K(`"${e}" restart "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function z(a){if(!a)throw new Error("summaryHook requires input");let{session_id:e}=a;await x();let s=new R,t=s.createSDKSession(e,"",""),r=s.getPromptCounter(t);s.close();let n=I();S.dataIn("HOOK","Stop: Requesting summary",{sessionId:t,workerPort:n,promptNumber:r});try{let o=await fetch(`http://127.0.0.1:${n}/sessions/${t}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:r}),signal:AbortSignal.timeout(2e3)});if(!o.ok){let i=await o.text();throw S.failure("HOOK","Failed to generate summary",{sessionId:t,status:o.status},i),new Error(`Failed to request summary from worker: ${o.status} ${i}`)}S.debug("HOOK","Summary request sent successfully",{sessionId:t})}catch(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):o}finally{await fetch(`http://127.0.0.1:${n}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})})}console.log(D("Stop",!0))}var L="";U.on("data",a=>L+=a);U.on("end",async()=>{let a=L?JSON.parse(L):void 0;await z(a)});
`;try{let l=this.db.prepare(m).all(p,u,...i),b=this.db.prepare(T).all(p,u,...i),c=this.db.prepare(g).all(p,u,...i);return{observations:l,sessions:b.map(d=>({id:d.id,sdk_session_id:d.sdk_session_id,project:d.project,request:d.request,completed:d.completed,next_steps:d.next_steps,created_at:d.created_at,created_at_epoch:d.created_at_epoch})),prompts:c.map(d=>({id:d.id,claude_session_id:d.claude_session_id,project:d.project,prompt:d.prompt_text,created_at:d.created_at,created_at_epoch:d.created_at_epoch}))}}catch(l){return console.error("[SessionStore] Error querying timeline records:",l.message),{observations:[],sessions:[],prompts:[]}}}close(){this.db.close()}};function $(a,e,s){return a==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:a==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:a==="UserPromptSubmit"||a==="PostToolUse"?{continue:!0,suppressOutput:!0}:a==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function D(a,e,s={}){let t=$(a,e,s);return JSON.stringify(t)}import f from"path";import{homedir as W}from"os";import{existsSync as G,readFileSync as Y}from"fs";import{execSync as K}from"child_process";var q=100,V=100,J=1e4;function I(){try{let a=f.join(W(),".claude-mem","settings.json");if(G(a)){let e=JSON.parse(Y(a,"utf-8")),s=parseInt(e.env?.CLAUDE_MEM_WORKER_PORT,10);if(!isNaN(s))return s}}catch{}return parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10)}async function k(){try{let a=I();return(await fetch(`http://127.0.0.1:${a}/health`,{signal:AbortSignal.timeout(q)})).ok}catch{return!1}}async function Q(){let a=Date.now();for(;Date.now()-a<J;){if(await k())return!0;await new Promise(e=>setTimeout(e,V))}return!1}async function x(){if(await k())return;let a=v(),e=f.join(a,"node_modules",".bin","pm2"),s=f.join(a,"ecosystem.config.cjs");if(K(`"${e}" start "${s}"`,{cwd:a,stdio:"pipe"}),!await Q())throw new Error("Worker failed to become healthy after restart")}async function z(a){if(!a)throw new Error("summaryHook requires input");let{session_id:e}=a;await x();let s=new R,t=s.createSDKSession(e,"",""),r=s.getPromptCounter(t);s.close();let n=I();S.dataIn("HOOK","Stop: Requesting summary",{sessionId:t,workerPort:n,promptNumber:r});try{let o=await fetch(`http://127.0.0.1:${n}/sessions/${t}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:r}),signal:AbortSignal.timeout(2e3)});if(!o.ok){let i=await o.text();throw S.failure("HOOK","Failed to generate summary",{sessionId:t,status:o.status},i),new Error(`Failed to request summary from worker: ${o.status} ${i}`)}S.debug("HOOK","Summary request sent successfully",{sessionId:t})}catch(o){throw o.cause?.code==="ECONNREFUSED"||o.name==="TimeoutError"||o.message.includes("fetch failed")?new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"):o}finally{await fetch(`http://127.0.0.1:${n}/api/processing`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({isProcessing:!1})})}console.log(D("Stop",!0))}var L="";U.on("data",a=>L+=a);U.on("end",async()=>{let a=L?JSON.parse(L):void 0;await z(a)});
File diff suppressed because one or more lines are too long
+363
View File
@@ -0,0 +1,363 @@
---
name: troubleshoot
description: Diagnose and fix claude-mem installation issues. Checks PM2 worker status, database integrity, service health, dependencies, and provides automated fixes for common problems.
---
# Claude-Mem Troubleshooting Skill
This skill diagnoses and resolves common installation and operational issues with the claude-mem plugin.
## Quick Reference
**Common Issues:**
- Memory not persisting after `/clear`
- Viewer UI empty or not loading
- Worker service not running
- Database missing or corrupted
- Port conflicts
- Missing dependencies
## Diagnostic Workflow
When invoked, follow these steps systematically:
### 1. Check PM2 Worker Status
First, verify if the worker service is running:
```bash
# Check if PM2 is available
which pm2 || echo "PM2 not found in PATH"
# List PM2 processes
pm2 jlist 2>&1
# If pm2 is not found, try the local installation
~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2 jlist 2>&1
```
**Expected output:** JSON array with `claude-mem-worker` process showing `"status": "online"`
**If worker not running or status is not "online":**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
# Or use local pm2:
node_modules/.bin/pm2 start ecosystem.config.cjs
```
### 2. Check Worker Service Health
Test if the worker service responds to HTTP requests:
```bash
# Default port is 37777
curl -s http://127.0.0.1:37777/health
# Check custom port from settings
PORT=$(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT | grep -o '[0-9]\+' || echo "37777")
curl -s http://127.0.0.1:$PORT/health
```
**Expected output:** `{"status":"ok"}`
**If connection refused:**
- Worker not running → Go back to step 1
- Port conflict → Check what's using the port:
```bash
lsof -i :37777 || netstat -tlnp | grep 37777
```
### 3. Check Database
Verify the database exists and contains data:
```bash
# Check if database file exists
ls -lh ~/.claude-mem/claude-mem.db
# Check database size (should be > 0 bytes)
du -h ~/.claude-mem/claude-mem.db
# Query database for observation count (requires sqlite3)
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as observation_count FROM observations;" 2>&1
# Query for session count
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) as session_count FROM sessions;" 2>&1
# Check recent observations
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, type, title FROM observations ORDER BY created_at DESC LIMIT 5;" 2>&1
```
**Expected:**
- Database file exists (typically 100KB - 10MB+)
- Contains observations and sessions
- Recent observations visible
**If database missing or empty:**
- New installation - this is normal, database will populate as you work
- After `/clear` - sessions are marked complete but not deleted, data should persist
- Corrupted database - backup and recreate:
```bash
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
# Worker will recreate on next observation
```
### 4. Check Dependencies Installation
Verify all required npm packages are installed:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
# Check for critical packages
ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1
ls node_modules/better-sqlite3 2>&1 | head -1
ls node_modules/express 2>&1 | head -1
ls node_modules/pm2 2>&1 | head -1
```
**Expected:** All critical packages present
**If dependencies missing:**
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
npm install
```
### 5. Check Worker Logs
Review recent worker logs for errors:
```bash
# View last 50 lines of worker logs
pm2 logs claude-mem-worker --lines 50 --nostream
# Or use local pm2:
cd ~/.claude/plugins/marketplaces/thedotmack/
node_modules/.bin/pm2 logs claude-mem-worker --lines 50 --nostream
# Check for specific errors
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error\|exception\|failed"
```
### 6. Test Viewer UI
Check if the web viewer is accessible:
```bash
# Test viewer endpoint
curl -s http://127.0.0.1:37777/ | head -20
# Test stats endpoint
curl -s http://127.0.0.1:37777/api/stats
```
**Expected:**
- `/` returns HTML page with React viewer
- `/api/stats` returns JSON with database counts
### 7. Check Port Configuration
Verify port settings and availability:
```bash
# Check if custom port is configured
cat ~/.claude-mem/settings.json 2>/dev/null
cat ~/.claude/settings.json 2>/dev/null
# Check what's listening on default port
lsof -i :37777 2>&1 || netstat -tlnp 2>&1 | grep 37777
# Test connectivity
nc -zv 127.0.0.1 37777 2>&1
```
## Automated Fix Sequence
If you're seeing issues, try this automated fix sequence:
```bash
# 1. Stop the worker
pm2 delete claude-mem-worker 2>/dev/null || true
# 2. Navigate to plugin directory
cd ~/.claude/plugins/marketplaces/thedotmack/
# 3. Ensure dependencies are installed
npm install
# 4. Start worker with local pm2
node_modules/.bin/pm2 start ecosystem.config.cjs
# 5. Wait for health check
sleep 3
curl -s http://127.0.0.1:37777/health
# 6. Check logs for any errors
node_modules/.bin/pm2 logs claude-mem-worker --lines 20 --nostream
```
## Common Issue Resolutions
### Issue: "Nothing is remembered after /clear"
**Root cause:** Sessions are marked complete but data should persist. This suggests:
- Worker not processing observations
- Database not being written to
- Context hook not reading from database
**Fix:**
1. Verify worker is running (Step 1)
2. Check database has recent observations (Step 3)
3. Restart worker and start new session
4. Create a test observation: `/skill version-bump` then cancel
5. Check if observation appears in viewer: http://127.0.0.1:37777
### Issue: "Viewer empty after every Claude restart"
**Root cause:**
- Database being recreated on startup (shouldn't happen)
- Worker reading from wrong database location
- Database permissions issue
**Fix:**
1. Check database file exists and has data (Step 3)
2. Check file permissions:
```bash
ls -la ~/.claude-mem/claude-mem.db
# Should be readable/writable by your user
```
3. Verify worker is using correct database path in logs
4. Test viewer connection manually
### Issue: "Old memory in Claude"
**Root cause:** Context hook injecting stale observations
**Fix:**
1. Check the observation count setting:
```bash
grep CLAUDE_MEM_CONTEXT_OBSERVATIONS ~/.claude/settings.json
```
2. Default is 50 observations - you can adjust this
3. Check database for actual observation dates:
```bash
sqlite3 ~/.claude-mem/claude-mem.db "SELECT created_at, project, title FROM observations ORDER BY created_at DESC LIMIT 10;"
```
### Issue: "Worker not starting"
**Root cause:**
- Port already in use
- PM2 not installed or not in PATH
- Missing dependencies
**Fix:**
1. Try manual worker start:
```bash
cd ~/.claude/plugins/marketplaces/thedotmack/
node plugin/scripts/worker-service.cjs
# Should start server on port 37777
```
2. If port in use, change it:
```bash
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
```
## Full System Diagnosis
Run this comprehensive diagnostic script:
```bash
#!/bin/bash
echo "=== Claude-Mem Troubleshooting Report ==="
echo ""
echo "1. Environment"
echo " OS: $(uname -s)"
echo ""
echo "2. Plugin Installation"
echo " Plugin directory exists: $([ -d ~/.claude/plugins/marketplaces/thedotmack ] && echo 'YES' || echo 'NO')"
echo " Package version: $(grep '"version"' ~/.claude/plugins/marketplaces/thedotmack/package.json 2>/dev/null | head -1)"
echo ""
echo "3. Database"
echo " Database exists: $([ -f ~/.claude-mem/claude-mem.db ] && echo 'YES' || echo 'NO')"
echo " Database size: $(du -h ~/.claude-mem/claude-mem.db 2>/dev/null | cut -f1)"
echo " Observation count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM observations;' 2>/dev/null || echo 'N/A')"
echo " Session count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;' 2>/dev/null || echo 'N/A')"
echo ""
echo "4. Worker Service"
PM2_PATH=$(which pm2 2>/dev/null || echo "~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2")
echo " PM2 path: $PM2_PATH"
WORKER_STATUS=$($PM2_PATH jlist 2>/dev/null | grep -o '"name":"claude-mem-worker".*"status":"[^"]*"' | grep -o 'status":"[^"]*"' | cut -d'"' -f3 || echo 'not running')
echo " Worker status: $WORKER_STATUS"
echo " Health check: $(curl -s http://127.0.0.1:37777/health 2>/dev/null || echo 'FAILED')"
echo ""
echo "5. Configuration"
echo " Port setting: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT || echo 'default (37777)')"
echo " Observation count: $(cat ~/.claude/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
echo ""
echo "6. Recent Activity"
echo " Latest observation: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM observations ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo " Latest session: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
echo ""
echo "=== End Report ==="
```
Save this as `/tmp/claude-mem-diagnostics.sh` and run:
```bash
bash /tmp/claude-mem-diagnostics.sh
```
## Reporting Issues
If troubleshooting doesn't resolve the issue, collect this information for a bug report:
1. Full diagnostic report (run script above)
2. Worker logs: `pm2 logs claude-mem-worker --lines 100 --nostream`
3. Your setup:
- Claude version: Check with Claude
- OS: `uname -a`
- Node version: `node --version`
- Plugin version: In package.json
4. Steps to reproduce the issue
5. Expected vs actual behavior
Post to: https://github.com/thedotmack/claude-mem/issues
## Prevention Tips
**Keep claude-mem healthy:**
- Regularly check viewer UI to see if observations are being captured
- Monitor database size (shouldn't grow unbounded)
- Update plugin when new versions are released
- Keep Claude Code updated
**Performance tuning:**
- Adjust `CLAUDE_MEM_CONTEXT_OBSERVATIONS` if context is too large/small
- Use `/clear` to mark sessions complete and start fresh
- Use MCP search tools to query specific memories instead of loading everything
## Quick Commands Reference
```bash
# Restart worker
pm2 restart claude-mem-worker
# View logs
pm2 logs claude-mem-worker
# Check health
curl http://127.0.0.1:37777/health
# View database stats
curl http://127.0.0.1:37777/api/stats
# Open viewer
open http://127.0.0.1:37777
# Delete and reinstall worker
pm2 delete claude-mem-worker
cd ~/.claude/plugins/marketplaces/thedotmack/
pm2 start ecosystem.config.cjs
```
+94 -66
View File
@@ -4,13 +4,38 @@
*/
import path from 'path';
import { homedir } from 'os';
import { existsSync, readFileSync } from 'fs';
import { stdin } from 'process';
import { SessionStore } from '../services/sqlite/SessionStore.js';
// Configuration: Read from environment or use defaults
const DISPLAY_OBSERVATION_COUNT = parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS || '50', 10);
// Summaries are supplementary - show last 10 for context but not configurable
const DISPLAY_SESSION_COUNT = 10;
/**
* Get context depth from settings
* Priority: ~/.claude/settings.json > env var > default
*/
function getContextDepth(): number {
try {
const settingsPath = path.join(homedir(), '.claude', 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
if (settings.env?.CLAUDE_MEM_CONTEXT_OBSERVATIONS) {
const count = parseInt(settings.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS, 10);
if (!isNaN(count) && count > 0) {
return count;
}
}
}
} catch {
// Fall through to env var or default
}
return parseInt(process.env.CLAUDE_MEM_CONTEXT_OBSERVATIONS || '50', 10);
}
// Configuration: Read from settings.json or environment
const DISPLAY_OBSERVATION_COUNT = getContextDepth();
const DISPLAY_SESSION_COUNT = 10; // Recent sessions for timeline context
const CHARS_PER_TOKEN_ESTIMATE = 4; // Rough estimate for token counting
const SUMMARY_LOOKAHEAD = 1; // Fetch one extra summary for offset calculation
export interface SessionStartInput {
session_id?: string;
@@ -50,11 +75,27 @@ interface Observation {
created_at_epoch: number;
}
interface SessionSummary {
id: number;
sdk_session_id: string;
request: string | null;
investigated: string | null;
learned: string | null;
completed: string | null;
next_steps: string | null;
created_at: string;
created_at_epoch: number;
}
// Helper: Parse JSON array safely
function parseJsonArray(json: string | null): string[] {
if (!json) return [];
const parsed = JSON.parse(json);
return Array.isArray(parsed) ? parsed : [];
try {
const parsed = JSON.parse(json);
return Array.isArray(parsed) ? parsed : [];
} catch (err) {
return [];
}
}
// Helper: Format date with time
@@ -92,8 +133,7 @@ function formatDate(dateStr: string): string {
// Helper: Estimate token count for text
function estimateTokens(text: string | null): number {
if (!text) return 0;
// Rough estimate: ~4 characters per token
return Math.ceil(text.length / 4);
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
}
// Helper: Convert absolute paths to relative paths
@@ -104,6 +144,16 @@ function toRelativePath(filePath: string, cwd: string): string {
return filePath;
}
// Helper: Render a summary field (investigated, learned, etc.)
function renderSummaryField(label: string, value: string | null, color: string, useColors: boolean): string[] {
if (!value) return [];
if (useColors) {
return [`${color}${label}:${colors.reset} ${value}`, ''];
}
return [`**${label}**: ${value}`, ''];
}
// Helper: Get all observations for given sessions
function getObservations(db: SessionStore, sessionIds: string[]): Observation[] {
if (sessionIds.length === 0) return [];
@@ -125,7 +175,7 @@ function getObservations(db: SessionStore, sessionIds: string[]): Observation[]
/**
* Context Hook Main Logic
*/
async function contextHook(input?: SessionStartInput, useColors: boolean = false, useIndexView: boolean = false): Promise<string> {
async function contextHook(input?: SessionStartInput, useColors: boolean = false): Promise<string> {
const cwd = input?.cwd ?? process.cwd();
const project = cwd ? path.basename(cwd) : 'unknown-project';
@@ -146,13 +196,14 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
`).all(project, DISPLAY_OBSERVATION_COUNT) as Observation[];
// Get recent summaries (optional - may not exist for recent sessions)
// Fetch one extra for offset calculation
const recentSummaries = db.db.prepare(`
SELECT id, sdk_session_id, request, investigated, learned, completed, next_steps, created_at, created_at_epoch
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`).all(project, DISPLAY_SESSION_COUNT + 1) as Array<{ id: number; sdk_session_id: string; request: string | null; investigated: string | null; learned: string | null; completed: string | null; next_steps: string | null; created_at: string; created_at_epoch: number }>;
`).all(project, DISPLAY_SESSION_COUNT + SUMMARY_LOOKAHEAD) as SessionSummary[];
// If we have neither observations nor summaries, show empty state
if (allObservations.length === 0 && recentSummaries.length === 0) {
@@ -210,28 +261,37 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
output.push('');
}
// Create unified timeline with both observations and summaries
// Prepare summaries for timeline display
// The most recent summary shows full details (investigated, learned, etc.)
// Older summaries only show as timeline markers (no link needed)
const mostRecentSummaryId = recentSummaries[0]?.id;
// Create offset summaries
const summariesWithOffset = displaySummaries.map((summary, i) => {
// Most recent keeps its own time, others offset to next summary's time
const nextSummary = i === 0 ? null : recentSummaries[i + 1];
interface SummaryTimelineItem extends SessionSummary {
displayEpoch: number;
displayTime: string;
shouldShowLink: boolean;
}
const summariesForTimeline: SummaryTimelineItem[] = displaySummaries.map((summary, i) => {
// For visual grouping, display each summary at the time range it covers
// Most recent: shows at its own time (current session)
// Older: shows at the previous (older) summary's time to mark the session range
const olderSummary = i === 0 ? null : recentSummaries[i + 1];
return {
...summary,
displayEpoch: nextSummary ? nextSummary.created_at_epoch : summary.created_at_epoch,
displayTime: nextSummary ? nextSummary.created_at : summary.created_at,
isMostRecent: summary.id === mostRecentSummaryId
displayEpoch: olderSummary ? olderSummary.created_at_epoch : summary.created_at_epoch,
displayTime: olderSummary ? olderSummary.created_at : summary.created_at,
shouldShowLink: summary.id !== mostRecentSummaryId
};
});
type TimelineItem =
| { type: 'observation'; data: Observation }
| { type: 'summary'; data: typeof summariesWithOffset[0] };
| { type: 'summary'; data: SummaryTimelineItem };
const timeline: TimelineItem[] = [
...timelineObs.map(obs => ({ type: 'observation' as const, data: obs })),
...summariesWithOffset.map(summary => ({ type: 'summary' as const, data: summary }))
...summariesForTimeline.map(summary => ({ type: 'summary' as const, data: summary }))
];
// Sort chronologically
@@ -242,18 +302,18 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
});
// Group by day for rendering
const dayTimelines = new Map<string, typeof timeline>();
const itemsByDay = new Map<string, TimelineItem[]>();
for (const item of timeline) {
const itemDate = item.type === 'observation' ? item.data.created_at : item.data.displayTime;
const day = formatDate(itemDate);
if (!dayTimelines.has(day)) {
dayTimelines.set(day, []);
if (!itemsByDay.has(day)) {
itemsByDay.set(day, []);
}
dayTimelines.get(day)!.push(item);
itemsByDay.get(day)!.push(item);
}
// Sort days chronologically
const sortedDays = Array.from(dayTimelines.entries()).sort((a, b) => {
const sortedDays = Array.from(itemsByDay.entries()).sort((a, b) => {
const aDate = new Date(a[0]).getTime();
const bDate = new Date(b[0]).getTime();
return aDate - bDate;
@@ -288,7 +348,7 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
// Render summary
const summary = item.data;
const summaryTitle = `${summary.request || 'Session started'} (${formatDateTime(summary.displayTime)})`;
const link = summary.isMostRecent ? '' : `claude-mem://session-summary/${summary.id}`;
const link = summary.shouldShowLink ? `claude-mem://session-summary/${summary.id}` : '';
if (useColors) {
const linkPart = link ? `${colors.dim}[${link}]${colors.reset}` : '';
@@ -383,41 +443,10 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
// Add full summary details for most recent session
const mostRecentSummary = recentSummaries[0];
if (mostRecentSummary && (mostRecentSummary.investigated || mostRecentSummary.learned || mostRecentSummary.completed || mostRecentSummary.next_steps)) {
if (mostRecentSummary.investigated) {
if (useColors) {
output.push(`${colors.blue}Investigated:${colors.reset} ${mostRecentSummary.investigated}`);
} else {
output.push(`**Investigated**: ${mostRecentSummary.investigated}`);
}
output.push('');
}
if (mostRecentSummary.learned) {
if (useColors) {
output.push(`${colors.yellow}Learned:${colors.reset} ${mostRecentSummary.learned}`);
} else {
output.push(`**Learned**: ${mostRecentSummary.learned}`);
}
output.push('');
}
if (mostRecentSummary.completed) {
if (useColors) {
output.push(`${colors.green}Completed:${colors.reset} ${mostRecentSummary.completed}`);
} else {
output.push(`**Completed**: ${mostRecentSummary.completed}`);
}
output.push('');
}
if (mostRecentSummary.next_steps) {
if (useColors) {
output.push(`${colors.magenta}Next Steps:${colors.reset} ${mostRecentSummary.next_steps}`);
} else {
output.push(`**Next Steps**: ${mostRecentSummary.next_steps}`);
}
output.push('');
}
output.push(...renderSummaryField('Investigated', mostRecentSummary.investigated, colors.blue, useColors));
output.push(...renderSummaryField('Learned', mostRecentSummary.learned, colors.yellow, useColors));
output.push(...renderSummaryField('Completed', mostRecentSummary.completed, colors.green, useColors));
output.push(...renderSummaryField('Next Steps', mostRecentSummary.next_steps, colors.magenta, useColors));
}
// Footer with MCP search instructions
@@ -433,12 +462,11 @@ async function contextHook(input?: SessionStartInput, useColors: boolean = false
}
// Entry Point - handle stdin/stdout
const useIndexView = process.argv.includes('--index');
const forceColors = process.argv.includes('--colors'); // Add this line
const forceColors = process.argv.includes('--colors');
if (stdin.isTTY || forceColors) { // Modify this line to include forceColors
if (stdin.isTTY || forceColors) {
// Running manually from terminal - print formatted output with colors
contextHook(undefined, true, useIndexView).then(contextOutput => {
contextHook(undefined, true).then(contextOutput => {
console.log(contextOutput);
process.exit(0);
});
@@ -448,7 +476,7 @@ if (stdin.isTTY || forceColors) { // Modify this line to include forceColors
stdin.on('data', (chunk) => input += chunk);
stdin.on('end', async () => {
const parsed = input.trim() ? JSON.parse(input) : undefined;
const contextOutput = await contextHook(parsed, false, useIndexView);
const contextOutput = await contextHook(parsed, false);
const result = {
hookSpecificOutput: {
hookEventName: "SessionStart",
+20 -13
View File
@@ -29,6 +29,8 @@ CRITICAL: Record what was BUILT/FIXED/DEPLOYED/CONFIGURED, not what you (the obs
User's Goal: ${userPrompt}
Date: ${new Date().toISOString().split('T')[0]}
SESSION LIFECYCLE: You will observe tool executions, create observations, generate progress summaries when requested, and receive continuation prompts as the session progresses.
WHAT TO RECORD
--------------
Focus on deliverables and capabilities:
@@ -154,26 +156,31 @@ export function buildObservationPrompt(obs: Observation): string {
}
/**
* Build prompt to generate request summary
* Build prompt to generate progress summary
*/
export function buildSummaryPrompt(session: SDKSession): string {
return `THIS REQUEST'S SUMMARY
===============
Think about the last request, and write a summary of what was done, what was learned, and what's next.
return `PROGRESS SUMMARY CHECKPOINT
===========================
Write progress notes of what was done, what was learned, and what's next. This is a checkpoint to capture progress so far.
IMPORTANT! DO NOT summarize the observation process itself - you are summarizing a DIFFERENT claude code session, not this one.
User's Original Request: ${session.user_prompt}
Respond in this XML format:
<summary>
<request>[What did the user request? Form a title that reflects the actual request: ${session.user_prompt}]</request>
<investigated>[Was anything explored? What was it?]</investigated>
<learned>[Did you learn anything? What was learned about how things work?]</learned>
<completed>[Did you do any work? What shipped? What does the system now do?]</completed>
<next_steps>[What are the next steps?]</next_steps>
<notes>[Additional insights]</notes>
<request>[Short title related to the most recent prompt]</request>
<investigated>[What has been explored so far? What was examined?]</investigated>
<learned>[What have you learned about how things work?]</learned>
<completed>[What work has been completed so far? What has shipped or changed?]</completed>
<next_steps>[What are you actively working on or planning to work on next in this session?]</next_steps>
<notes>[Additional insights or observations about the current progress]</notes>
</summary>
IMPORTANT: This is not the end of the session. You will receive more requests to process, and more tool usages to observe and record. The summary helps keep track of progress. Always write at least a minimal summary explaining where we are at currently, even if you didn't learn anything new or complete any work.`;
FRAMING: This is a mid-session progress checkpoint. The session is ongoing - you may receive more requests and tool executions after this summary. Write "next_steps" as the current trajectory of work (what's actively being worked on or coming up next), not as post-session future work. Always write at least a minimal summary explaining current progress, even if work is still in early stages.`;
}
/**
* Build prompt for continuation of existing session
*/
export function buildContinuationPrompt(userPrompt: string, promptNumber: number): string {
return `User's request #${promptNumber}: ${userPrompt}`;
}
+5 -3
View File
@@ -16,7 +16,7 @@ import { DatabaseManager } from './DatabaseManager.js';
import { SessionManager } from './SessionManager.js';
import { logger } from '../../utils/logger.js';
import { parseObservations, parseSummary } from '../../sdk/parser.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt } from '../../sdk/prompts.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import type { ActiveSession, SDKUserMessage, PendingMessage } from '../worker-types.js';
// Import Agent SDK (assumes it's installed)
@@ -110,12 +110,14 @@ export class SDKAgent {
* Create event-driven message generator (yields messages from SessionManager)
*/
private async *createMessageGenerator(session: ActiveSession): AsyncIterableIterator<SDKUserMessage> {
// Yield initial user prompt with context
// Yield initial user prompt with context (or continuation if prompt #2+)
yield {
type: 'user',
message: {
role: 'user',
content: buildInitPrompt(session.project, session.claudeSessionId, session.userPrompt)
content: session.lastPromptNumber === 1
? buildInitPrompt(session.project, session.claudeSessionId, session.userPrompt)
: buildContinuationPrompt(session.userPrompt, session.lastPromptNumber)
},
session_id: session.claudeSessionId,
parent_tool_use_id: null,
+1 -1
View File
@@ -45,7 +45,7 @@ export class SessionManager {
pendingMessages: [],
abortController: new AbortController(),
generatorPromise: null,
lastPromptNumber: 0,
lastPromptNumber: this.dbManager.getSessionStore().getPromptCounter(sessionDbId),
startTime: Date.now()
};
+1 -1
View File
@@ -70,7 +70,7 @@ export async function ensureWorkerRunning(): Promise<void> {
const pm2Path = path.join(packageRoot, "node_modules", ".bin", "pm2");
const ecosystemPath = path.join(packageRoot, "ecosystem.config.cjs");
execSync(`"${pm2Path}" restart "${ecosystemPath}"`, {
execSync(`"${pm2Path}" start "${ecosystemPath}"`, {
cwd: packageRoot,
stdio: 'pipe'
});