2026-01-23 15:08:07 +08:00
#!/usr/bin/env node
/ * *
* Stop Hook ( Session End ) - Persist learnings when session ends
*
* Cross - platform ( Windows , macOS , Linux )
*
2026-02-11 23:56:41 -08:00
* Runs when Claude session ends . Extracts a meaningful summary from
* the session transcript ( via CLAUDE _TRANSCRIPT _PATH ) and saves it
* to a session file for cross - session continuity .
2026-01-23 15:08:07 +08:00
* /
const path = require ( 'path' ) ;
const fs = require ( 'fs' ) ;
const {
getSessionsDir ,
getDateString ,
getTimeString ,
2026-01-25 18:21:27 -08:00
getSessionIdShort ,
2026-01-23 15:08:07 +08:00
ensureDir ,
2026-02-11 23:56:41 -08:00
readFile ,
2026-01-23 15:08:07 +08:00
writeFile ,
replaceInFile ,
log
} = require ( '../lib/utils' ) ;
2026-02-11 23:56:41 -08:00
/ * *
* Extract a meaningful summary from the session transcript .
* Reads the JSONL transcript and pulls out key information :
* - User messages ( tasks requested )
* - Tools used
* - Files modified
* /
function extractSessionSummary ( transcriptPath ) {
const content = readFile ( transcriptPath ) ;
if ( ! content ) return null ;
const lines = content . split ( '\n' ) . filter ( Boolean ) ;
const userMessages = [ ] ;
const toolsUsed = new Set ( ) ;
const filesModified = new Set ( ) ;
2026-02-12 07:06:53 -08:00
let parseErrors = 0 ;
2026-02-11 23:56:41 -08:00
for ( const line of lines ) {
try {
const entry = JSON . parse ( line ) ;
// Collect user messages (first 200 chars each)
if ( entry . type === 'user' || entry . role === 'user' ) {
const text = typeof entry . content === 'string'
? entry . content
: Array . isArray ( entry . content )
? entry . content . map ( c => c . text || '' ) . join ( ' ' )
: '' ;
if ( text . trim ( ) ) {
userMessages . push ( text . trim ( ) . slice ( 0 , 200 ) ) ;
}
}
// Collect tool names and modified files
if ( entry . type === 'tool_use' || entry . tool _name ) {
const toolName = entry . tool _name || entry . name || '' ;
if ( toolName ) toolsUsed . add ( toolName ) ;
const filePath = entry . tool _input ? . file _path || entry . input ? . file _path || '' ;
if ( filePath && ( toolName === 'Edit' || toolName === 'Write' ) ) {
filesModified . add ( filePath ) ;
}
}
} catch {
2026-02-12 07:06:53 -08:00
parseErrors ++ ;
2026-02-11 23:56:41 -08:00
}
}
2026-02-12 07:06:53 -08:00
if ( parseErrors > 0 ) {
log ( ` [SessionEnd] Skipped ${ parseErrors } / ${ lines . length } unparseable transcript lines ` ) ;
}
2026-02-11 23:56:41 -08:00
if ( userMessages . length === 0 ) return null ;
return {
userMessages : userMessages . slice ( - 10 ) , // Last 10 user messages
toolsUsed : Array . from ( toolsUsed ) . slice ( 0 , 20 ) ,
filesModified : Array . from ( filesModified ) . slice ( 0 , 30 ) ,
totalMessages : userMessages . length
} ;
}
2026-01-23 15:08:07 +08:00
async function main ( ) {
const sessionsDir = getSessionsDir ( ) ;
const today = getDateString ( ) ;
2026-01-25 18:21:27 -08:00
const shortId = getSessionIdShort ( ) ;
const sessionFile = path . join ( sessionsDir , ` ${ today } - ${ shortId } -session.tmp ` ) ;
2026-01-23 15:08:07 +08:00
ensureDir ( sessionsDir ) ;
const currentTime = getTimeString ( ) ;
2026-02-11 23:56:41 -08:00
// Try to extract summary from transcript
const transcriptPath = process . env . CLAUDE _TRANSCRIPT _PATH ;
let summary = null ;
2026-02-12 13:40:14 -08:00
if ( transcriptPath ) {
if ( fs . existsSync ( transcriptPath ) ) {
summary = extractSessionSummary ( transcriptPath ) ;
} else {
log ( ` [SessionEnd] Transcript not found: ${ transcriptPath } ` ) ;
}
2026-02-11 23:56:41 -08:00
}
2026-01-23 15:08:07 +08:00
if ( fs . existsSync ( sessionFile ) ) {
2026-02-11 23:56:41 -08:00
// Update existing session file
2026-02-12 13:40:14 -08:00
const updated = replaceInFile (
2026-01-23 15:08:07 +08:00
sessionFile ,
/\*\*Last Updated:\*\*.*/ ,
` **Last Updated:** ${ currentTime } `
) ;
2026-02-12 13:40:14 -08:00
if ( ! updated ) {
log ( ` [SessionEnd] Failed to update timestamp in ${ sessionFile } ` ) ;
}
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
// If we have a new summary and the file still has the blank template, replace it
if ( summary ) {
const existing = readFile ( sessionFile ) ;
if ( existing && existing . includes ( '[Session context goes here]' ) ) {
const updatedContent = existing . replace (
/## Current State\n\n\[Session context goes here\]\n\n### Completed\n- \[ \]\n\n### In Progress\n- \[ \]\n\n### Notes for Next Session\n-\n\n### Context to Load\n```\n\[relevant files\]\n```/ ,
buildSummarySection ( summary )
) ;
writeFile ( sessionFile , updatedContent ) ;
}
2026-01-23 15:08:07 +08:00
}
2026-02-11 23:56:41 -08:00
log ( ` [SessionEnd] Updated session file: ${ sessionFile } ` ) ;
2026-01-23 15:08:07 +08:00
} else {
2026-02-11 23:56:41 -08:00
// Create new session file
const summarySection = summary
? buildSummarySection ( summary )
: ` ## Current State \n \n [Session context goes here] \n \n ### Completed \n - [ ] \n \n ### In Progress \n - [ ] \n \n ### Notes for Next Session \n - \n \n ### Context to Load \n \` \` \` \n [relevant files] \n \` \` \` ` ;
2026-01-23 15:08:07 +08:00
const template = ` # Session: ${ today }
* * Date : * * $ { today }
* * Started : * * $ { currentTime }
* * Last Updated : * * $ { currentTime }
-- -
2026-02-11 23:56:41 -08:00
$ { summarySection }
` ;
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
writeFile ( sessionFile , template ) ;
log ( ` [SessionEnd] Created session file: ${ sessionFile } ` ) ;
}
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
process . exit ( 0 ) ;
}
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
function buildSummarySection ( summary ) {
let section = '## Session Summary\n\n' ;
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
// Tasks (from user messages)
section += '### Tasks\n' ;
for ( const msg of summary . userMessages ) {
section += ` - ${ msg } \n ` ;
}
section += '\n' ;
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
// Files modified
if ( summary . filesModified . length > 0 ) {
section += '### Files Modified\n' ;
for ( const f of summary . filesModified ) {
section += ` - ${ f } \n ` ;
}
section += '\n' ;
}
2026-01-23 15:08:07 +08:00
2026-02-11 23:56:41 -08:00
// Tools used
if ( summary . toolsUsed . length > 0 ) {
section += ` ### Tools Used \n ${ summary . toolsUsed . join ( ', ' ) } \n \n ` ;
2026-01-23 15:08:07 +08:00
}
2026-02-11 23:56:41 -08:00
section += ` ### Stats \n - Total user messages: ${ summary . totalMessages } \n ` ;
return section ;
2026-01-23 15:08:07 +08:00
}
main ( ) . catch ( err => {
console . error ( '[SessionEnd] Error:' , err . message ) ;
process . exit ( 0 ) ;
} ) ;