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:
Alex Newman
2025-12-09 22:45:22 -05:00
committed by GitHub
parent 84c4d62812
commit eaba21329c
24 changed files with 342 additions and 396 deletions
+57
View File
@@ -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 '';
}