Refactor hooks codebase: reduce complexity and improve maintainability (#204)
* refactor: Clean up hook-response and new-hook files - Removed 'PreCompact' hook type and associated logic from hook-response.ts for improved type safety. - Deleted extensive architecture comments in new-hook.ts to streamline code readability. - Simplified debug logging in new-hook.ts to reduce verbosity. - Enhanced ensureWorkerRunning function in worker-utils.ts with a final health check before throwing errors. - Added a new documentation file outlining the hooks cleanup process and future improvements. * Refactor cleanup and user message hooks - Updated cleanup-hook.js to improve error handling and remove unnecessary input checks. - Simplified user-message-hook.js by removing time-sensitive announcements and streamlining output. - Enhanced logging functionality in both hooks for better debugging and clarity. * Refactor error handling in hooks to use centralized error handler - Introduced `handleWorkerError` function in `src/shared/hook-error-handler.ts` to manage worker-related errors. - Updated `context-hook.ts`, `new-hook.ts`, `save-hook.ts`, and `summary-hook.ts` to utilize the new error handler, simplifying error management and improving code readability. - Removed repetitive error handling logic from individual hooks, ensuring consistent user-friendly messages for connection issues. * Refactor user-message and summary hooks to utilize shared transcript parser; introduce hook exit codes - Moved user message extraction logic to a new shared module `transcript-parser.ts` for better code reuse. - Updated `summary-hook.ts` to use the new `extractLastMessage` function for retrieving user and assistant messages. - Replaced direct exit code usage in `user-message-hook.ts` with constants from `hook-constants.ts` for improved readability and maintainability. - Added `HOOK_EXIT_CODES` to `hook-constants.ts` to standardize exit codes across hooks. * Refactor hook input interfaces to enforce required fields - Updated `SessionStartInput`, `UserPromptSubmitInput`, `PostToolUseInput`, and `StopInput` interfaces to require `session_id`, `transcript_path`, and `cwd` fields, ensuring better type safety and clarity in hook inputs. - Removed optional index signatures from these interfaces to prevent unintended properties and improve code maintainability. - Adjusted related hook implementations to align with the new interface definitions. * Refactor save-hook to remove tool skipping logic; enhance summary-hook to handle spinner stopping with error logging; update SessionRoutes to load skip tools from settings; add CLAUDE_MEM_SKIP_TOOLS to SettingsDefaultsManager for configurable tool exclusion. * Document CLAUDE_MEM_SKIP_TOOLS setting in public docs Added documentation for the new CLAUDE_MEM_SKIP_TOOLS configuration setting in response to PR review feedback. Users can now discover and customize which tools are excluded from observations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export interface SettingsDefaults {
|
||||
CLAUDE_MEM_MODEL: string;
|
||||
CLAUDE_MEM_CONTEXT_OBSERVATIONS: string;
|
||||
CLAUDE_MEM_WORKER_PORT: string;
|
||||
CLAUDE_MEM_SKIP_TOOLS: string;
|
||||
// System Configuration
|
||||
CLAUDE_MEM_DATA_DIR: string;
|
||||
CLAUDE_MEM_LOG_LEVEL: string;
|
||||
@@ -45,6 +46,7 @@ export class SettingsDefaultsManager {
|
||||
CLAUDE_MEM_MODEL: 'claude-haiku-4-5',
|
||||
CLAUDE_MEM_CONTEXT_OBSERVATIONS: '50',
|
||||
CLAUDE_MEM_WORKER_PORT: '37777',
|
||||
CLAUDE_MEM_SKIP_TOOLS: 'ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion',
|
||||
// System Configuration
|
||||
CLAUDE_MEM_DATA_DIR: join(homedir(), '.claude-mem'),
|
||||
CLAUDE_MEM_LOG_LEVEL: 'INFO',
|
||||
|
||||
@@ -6,6 +6,16 @@ export const HOOK_TIMEOUTS = {
|
||||
WINDOWS_MULTIPLIER: 1.5 // Platform-specific adjustment
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Hook exit codes for Claude Code
|
||||
*/
|
||||
export const HOOK_EXIT_CODES = {
|
||||
SUCCESS: 0,
|
||||
FAILURE: 1,
|
||||
/** Show user message that Claude does NOT receive as context */
|
||||
USER_MESSAGE_ONLY: 3,
|
||||
} as const;
|
||||
|
||||
export function getTimeout(baseTimeout: number): number {
|
||||
return process.platform === 'win32'
|
||||
? Math.round(baseTimeout * HOOK_TIMEOUTS.WINDOWS_MULTIPLIER)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Handles fetch errors by providing user-friendly messages for connection issues
|
||||
* @throws Error with helpful message if worker is unreachable, re-throws original otherwise
|
||||
*/
|
||||
export function handleWorkerError(error: any): never {
|
||||
if (error.cause?.code === 'ECONNREFUSED' ||
|
||||
error.name === 'TimeoutError' ||
|
||||
error.message?.includes('fetch failed')) {
|
||||
throw new Error(
|
||||
"There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue"
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
/**
|
||||
* Extract last message of specified role from transcript JSONL file
|
||||
* @param transcriptPath Path to transcript file
|
||||
* @param role 'user' or 'assistant'
|
||||
* @param stripSystemReminders Whether to remove <system-reminder> tags (for assistant)
|
||||
*/
|
||||
export function extractLastMessage(
|
||||
transcriptPath: string,
|
||||
role: 'user' | 'assistant',
|
||||
stripSystemReminders: boolean = false
|
||||
): string {
|
||||
if (!transcriptPath || !existsSync(transcriptPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(transcriptPath, 'utf-8').trim();
|
||||
if (!content) return '';
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const line = JSON.parse(lines[i]);
|
||||
if (line.type === role && line.message?.content) {
|
||||
let text = '';
|
||||
const msgContent = line.message.content;
|
||||
|
||||
if (typeof msgContent === 'string') {
|
||||
text = msgContent;
|
||||
} else if (Array.isArray(msgContent)) {
|
||||
text = msgContent
|
||||
.filter((c: any) => c.type === 'text')
|
||||
.map((c: any) => c.text)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
if (stripSystemReminders) {
|
||||
text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '');
|
||||
text = text.replace(/\n{3,}/g, '\n\n').trim();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'Failed to read transcript', { transcriptPath }, error as Error);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
@@ -156,6 +156,12 @@ export async function ensureWorkerRunning(): Promise<void> {
|
||||
// Try to start the worker
|
||||
const started = await startWorker();
|
||||
|
||||
// Final health check before throwing error
|
||||
// Worker might be already running but was temporarily unresponsive
|
||||
if (!started && await isWorkerHealthy()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
const port = getWorkerPort();
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user