Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { existsSync, unlinkSync } from 'fs';
|
||||
import { HooksDatabase } from '../services/sqlite/HooksDatabase.js';
|
||||
import { getWorkerSocketPath } from '../shared/paths.js';
|
||||
|
||||
export interface SessionEndInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
transcript_path?: string;
|
||||
hook_event_name: string;
|
||||
reason: 'exit' | 'clear' | 'logout' | 'prompt_input_exit' | 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Hook - SessionEnd
|
||||
* Cleans up worker process and marks session as terminated
|
||||
*
|
||||
* This hook runs when a Claude Code session ends. It:
|
||||
* 1. Finds active SDK session for this Claude session
|
||||
* 2. Terminates worker process if still running
|
||||
* 3. Removes stale socket file
|
||||
* 4. Marks session as failed (since no Stop hook completed it)
|
||||
*/
|
||||
export function cleanupHook(input?: SessionEndInput): void {
|
||||
try {
|
||||
// Log hook entry point
|
||||
console.error('[claude-mem cleanup] Hook fired', {
|
||||
input: input ? {
|
||||
session_id: input.session_id,
|
||||
cwd: input.cwd,
|
||||
reason: input.reason
|
||||
} : null
|
||||
});
|
||||
|
||||
// Handle standalone execution (no input provided)
|
||||
if (!input) {
|
||||
console.log('No input provided - this script is designed to run as a Claude Code SessionEnd hook');
|
||||
console.log('\nExpected input format:');
|
||||
console.log(JSON.stringify({
|
||||
session_id: "string",
|
||||
cwd: "string",
|
||||
transcript_path: "string",
|
||||
hook_event_name: "SessionEnd",
|
||||
reason: "exit"
|
||||
}, null, 2));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const { session_id, reason } = input;
|
||||
console.error('[claude-mem cleanup] Searching for active SDK session', { session_id, reason });
|
||||
|
||||
// Find active SDK session
|
||||
const db = new HooksDatabase();
|
||||
const session = db.findActiveSDKSession(session_id);
|
||||
|
||||
if (!session) {
|
||||
// No active session - nothing to clean up
|
||||
console.error('[claude-mem cleanup] No active SDK session found', { session_id });
|
||||
db.close();
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error('[claude-mem cleanup] Active SDK session found', {
|
||||
session_id: session.id,
|
||||
sdk_session_id: session.sdk_session_id,
|
||||
project: session.project
|
||||
});
|
||||
|
||||
// Get worker PID and socket path
|
||||
const socketPath = getWorkerSocketPath(session.id);
|
||||
|
||||
// 1. Kill worker process if it exists
|
||||
try {
|
||||
// Try to read PID from socket file existence
|
||||
if (existsSync(socketPath)) {
|
||||
console.error('[claude-mem cleanup] Socket file exists, attempting cleanup', { socketPath });
|
||||
|
||||
// Remove socket file
|
||||
try {
|
||||
unlinkSync(socketPath);
|
||||
console.error('[claude-mem cleanup] Socket file removed successfully', { socketPath });
|
||||
} catch (unlinkErr: any) {
|
||||
console.error('[claude-mem cleanup] Failed to remove socket file', {
|
||||
error: unlinkErr.message,
|
||||
socketPath
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error('[claude-mem cleanup] Socket file does not exist', { socketPath });
|
||||
}
|
||||
|
||||
// Note: We don't kill the worker process here because:
|
||||
// 1. Workers have a 2-hour watchdog timer that will kill them automatically
|
||||
// 2. Killing by PID is fragile (PID might be reused)
|
||||
// 3. The worker will exit on its own when it can't reach the socket
|
||||
// We just clean up the socket file to prevent stale socket issues
|
||||
|
||||
} catch (cleanupErr: any) {
|
||||
console.error('[claude-mem cleanup] Error during cleanup', {
|
||||
error: cleanupErr.message,
|
||||
stack: cleanupErr.stack
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Mark session as failed (since Stop hook didn't complete it)
|
||||
try {
|
||||
db.markSessionFailed(session.id);
|
||||
console.error('[claude-mem cleanup] Session marked as failed', {
|
||||
session_id: session.id,
|
||||
reason: 'SessionEnd hook - session terminated without completion'
|
||||
});
|
||||
} catch (markErr: any) {
|
||||
console.error('[claude-mem cleanup] Failed to mark session as failed', {
|
||||
error: markErr.message,
|
||||
session_id: session.id
|
||||
});
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
console.error('[claude-mem cleanup] Cleanup completed successfully');
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error: any) {
|
||||
// On error, don't block Claude Code exit
|
||||
console.error('[claude-mem cleanup] Unexpected error in hook', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
});
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
+64
-13
@@ -3,8 +3,9 @@ import path from 'path';
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
source?: string;
|
||||
transcript_path: string;
|
||||
hook_event_name: string;
|
||||
source: "startup" | "resume" | "clear" | "compact";
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -14,37 +15,59 @@ export interface SessionStartInput {
|
||||
*/
|
||||
export function contextHook(input?: SessionStartInput): void {
|
||||
try {
|
||||
// Log hook invocation
|
||||
console.error('[claude-mem context] Hook fired with input:', JSON.stringify({
|
||||
session_id: input?.session_id,
|
||||
transcript_path: input?.transcript_path,
|
||||
hook_event_name: input?.hook_event_name,
|
||||
source: input?.source,
|
||||
has_input: !!input
|
||||
}));
|
||||
|
||||
// Handle standalone execution (no input provided)
|
||||
if (!input) {
|
||||
console.error('[claude-mem context] No input provided - exiting (standalone mode)');
|
||||
console.log('No input provided - this script is designed to run as a Claude Code SessionStart hook');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Only run on startup (not on resume)
|
||||
if (input.source && input.source !== 'startup') {
|
||||
console.log(''); // Output nothing, just exit
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Extract project from cwd
|
||||
const project = path.basename(input.cwd);
|
||||
// Extract project from transcript_path
|
||||
// Path format: ~/.claude/projects/{project-name}/{session-id}.jsonl
|
||||
const transcriptDir = path.dirname(input.transcript_path);
|
||||
const project = path.basename(transcriptDir);
|
||||
console.error('[claude-mem context] Extracted project name:', project, 'from transcript_path:', input.transcript_path);
|
||||
|
||||
// Get recent summaries
|
||||
console.error('[claude-mem context] Querying database for recent summaries...');
|
||||
const db = new HooksDatabase();
|
||||
const summaries = db.getRecentSummaries(project, 5);
|
||||
db.close();
|
||||
|
||||
console.error('[claude-mem context] Database query complete - found', summaries.length, 'summaries');
|
||||
|
||||
// Log preview of each summary found
|
||||
if (summaries.length > 0) {
|
||||
console.error('[claude-mem context] Summary previews:');
|
||||
summaries.forEach((summary, idx) => {
|
||||
const preview = summary.request?.substring(0, 100) || summary.completed?.substring(0, 100) || '(no content)';
|
||||
console.error(` [${idx + 1}]`, preview + (preview.length >= 100 ? '...' : ''));
|
||||
});
|
||||
}
|
||||
|
||||
// If no summaries, provide helpful message
|
||||
if (summaries.length === 0) {
|
||||
console.error('[claude-mem context] No summaries found - outputting empty context message');
|
||||
console.log('# Recent Session Context\n\nNo previous sessions found for this project yet.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Format output for Claude
|
||||
console.error('[claude-mem context] Building markdown context from summaries...');
|
||||
const output: string[] = [];
|
||||
output.push('# Recent Session Context');
|
||||
output.push('');
|
||||
output.push(`Here's what happened in recent ${project} sessions:`);
|
||||
const sessionWord = summaries.length === 1 ? 'session' : 'sessions';
|
||||
output.push(`Showing last ${summaries.length} ${sessionWord} for **${project}**:`);
|
||||
output.push('');
|
||||
|
||||
for (const summary of summaries) {
|
||||
@@ -67,6 +90,22 @@ export function contextHook(input?: SessionStartInput): void {
|
||||
output.push(`**Next Steps:** ${summary.next_steps}`);
|
||||
}
|
||||
|
||||
// Show files that were read during the session
|
||||
if (summary.files_read) {
|
||||
try {
|
||||
const files = JSON.parse(summary.files_read);
|
||||
if (Array.isArray(files) && files.length > 0) {
|
||||
output.push(`**Files Read:** ${files.join(', ')}`);
|
||||
}
|
||||
} catch {
|
||||
// Backwards compatibility: if not valid JSON, show as text
|
||||
if (summary.files_read.trim()) {
|
||||
output.push(`**Files Read:** ${summary.files_read}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show files that were edited/written during the session
|
||||
if (summary.files_edited) {
|
||||
try {
|
||||
const files = JSON.parse(summary.files_edited);
|
||||
@@ -85,13 +124,25 @@ export function contextHook(input?: SessionStartInput): void {
|
||||
output.push('');
|
||||
}
|
||||
|
||||
// Log details about the markdown output
|
||||
const markdownOutput = output.join('\n');
|
||||
console.error('[claude-mem context] Markdown built successfully');
|
||||
console.error('[claude-mem context] Output length:', markdownOutput.length, 'characters,', output.length, 'lines');
|
||||
console.error('[claude-mem context] Output preview (first 200 chars):', markdownOutput.substring(0, 200) + '...');
|
||||
console.error('[claude-mem context] Outputting context to stdout for Claude Code injection');
|
||||
|
||||
// Output to stdout for Claude Code to inject
|
||||
console.log(output.join('\n'));
|
||||
console.log(markdownOutput);
|
||||
|
||||
console.error('[claude-mem context] Context hook completed successfully');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error: any) {
|
||||
// On error, exit silently - don't block Claude Code
|
||||
console.error(`[claude-mem context error: ${error.message}]`);
|
||||
console.error('[claude-mem context] ERROR occurred during context hook execution');
|
||||
console.error('[claude-mem context] Error message:', error.message);
|
||||
console.error('[claude-mem context] Error stack:', error.stack);
|
||||
console.error('[claude-mem context] Exiting gracefully to avoid blocking Claude Code');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
+30
-2
@@ -14,6 +14,11 @@ export interface StopInput {
|
||||
*/
|
||||
export function summaryHook(input?: StopInput): void {
|
||||
try {
|
||||
// Log hook entry point
|
||||
console.error('[claude-mem summary] Hook fired', {
|
||||
input: input ? { session_id: input.session_id, cwd: input.cwd } : null
|
||||
});
|
||||
|
||||
// Handle standalone execution (no input provided)
|
||||
if (!input) {
|
||||
console.log('No input provided - this script is designed to run as a Claude Code Stop hook');
|
||||
@@ -26,6 +31,7 @@ export function summaryHook(input?: StopInput): void {
|
||||
}
|
||||
|
||||
const { session_id } = input;
|
||||
console.error('[claude-mem summary] Searching for active SDK session', { session_id });
|
||||
|
||||
// Find active SDK session
|
||||
const db = new HooksDatabase();
|
||||
@@ -34,10 +40,17 @@ export function summaryHook(input?: StopInput): void {
|
||||
|
||||
if (!session) {
|
||||
// No active session - nothing to finalize
|
||||
console.error('[claude-mem summary] No active SDK session found', { session_id });
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error('[claude-mem summary] Active SDK session found', {
|
||||
session_id: session.id,
|
||||
collection_name: session.collection_name,
|
||||
worker_pid: session.worker_pid
|
||||
});
|
||||
|
||||
// Get socket path
|
||||
const socketPath = getWorkerSocketPath(session.id);
|
||||
|
||||
@@ -46,25 +59,40 @@ export function summaryHook(input?: StopInput): void {
|
||||
type: 'finalize'
|
||||
};
|
||||
|
||||
console.error('[claude-mem summary] Attempting to send FINALIZE message to worker socket', {
|
||||
socketPath,
|
||||
message
|
||||
});
|
||||
|
||||
const client = net.connect(socketPath, () => {
|
||||
console.error('[claude-mem summary] Socket connection established, sending message');
|
||||
client.write(JSON.stringify(message) + '\n');
|
||||
client.end();
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
// Socket not available - worker may have already finished or crashed
|
||||
console.error(`[claude-mem summary] Socket error: ${err.message}`);
|
||||
console.error('[claude-mem summary] Socket error occurred', {
|
||||
error: err.message,
|
||||
code: (err as any).code,
|
||||
socketPath
|
||||
});
|
||||
// Continue anyway, don't block Claude
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.error('[claude-mem summary] Socket connection closed successfully');
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
// On error, don't block Claude Code
|
||||
console.error(`[claude-mem summary error: ${error.message}]`);
|
||||
console.error('[claude-mem summary] Unexpected error in hook', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
});
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user