/** * Happy Path Error With Fallback * * Semantic meaning: "When the happy path fails, this is an error, but we have a fallback." * * Logs to ~/.claude-mem/silent.log and returns a fallback value. * Check logs with `npm run logs:silent` * * Use happy_path_error__with_fallback for: * ✅ Unexpected null/undefined values that should theoretically never happen * ✅ Defensive coding where silent fallback is acceptable * ✅ Situations where you want to track unexpected nulls without breaking execution * * DO NOT use for: * ❌ Nullable fields with valid default behavior (use direct || defaults) * ❌ Critical validation failures (use logger.warn or throw Error) * ❌ Try-catch blocks where error is already logged (redundant) * * Good examples: * // Truly unexpected null (should never happen in theory) * const id = session.id || happy_path_error__with_fallback('session.id missing', { session }); * * Bad examples (use direct defaults instead): * // Nullable field with valid empty default * const title = obs.title || happy_path_error__with_fallback('obs.title missing', { obs }, '(untitled)'); * // BETTER: const title = obs.title || '(untitled)'; * * // Array that can validly be undefined/null * const count = obs.files?.length ?? (happy_path_error__with_fallback('obs.files missing', { obs }), 0); * // BETTER: const count = obs.files?.length ?? 0; */ import { appendFileSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; const LOG_FILE = join(homedir(), '.claude-mem', 'silent.log'); /** * Write an error message to silent.log and return fallback value * @param message - Error message describing what went wrong * @param data - Optional data to include (will be JSON stringified) * @param fallback - Value to return (defaults to empty string) * @returns The fallback value (for use in || fallbacks) */ export function happy_path_error__with_fallback(message: string, data?: any, fallback: string = ''): string { const timestamp = new Date().toISOString(); // Capture stack trace to get caller location const stack = new Error().stack || ''; const stackLines = stack.split('\n'); // Line 0: "Error" // Line 1: "at silentDebug ..." // Line 2: "at ..." <- We want this one const callerLine = stackLines[2] || ''; const callerMatch = callerLine.match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/); const location = callerMatch ? `${callerMatch[1].split('/').pop()}:${callerMatch[2]}` : 'unknown'; let logLine = `[${timestamp}] [HAPPY-PATH-ERROR] [${location}] ${message}`; if (data !== undefined) { try { logLine += ` ${JSON.stringify(data)}`; } catch (error) { logLine += ` [stringify error: ${error}]`; } } logLine += '\n'; try { appendFileSync(LOG_FILE, logLine); } catch (error) { // If we can't write to the log file, fail silently (it's a debug utility after all) // Only write to stderr as a last resort console.error('[silent-debug] Failed to write to log:', error); } return fallback; } /** * Clear the silent log file */ export function clearSilentLog(): void { try { appendFileSync(LOG_FILE, `\n${'='.repeat(80)}\n[${new Date().toISOString()}] Log cleared\n${'='.repeat(80)}\n\n`); } catch (error) { // Expected: Log file may not be writable } }