source dump of claude code
at main 157 lines 5.1 kB view raw
1import memoize from 'lodash-es/memoize.js' 2 3export type DebugFilter = { 4 include: string[] 5 exclude: string[] 6 isExclusive: boolean 7} 8 9/** 10 * Parse debug filter string into a filter configuration 11 * Examples: 12 * - "api,hooks" -> include only api and hooks categories 13 * - "!1p,!file" -> exclude logging and file categories 14 * - undefined/empty -> no filtering (show all) 15 */ 16export const parseDebugFilter = memoize( 17 (filterString?: string): DebugFilter | null => { 18 if (!filterString || filterString.trim() === '') { 19 return null 20 } 21 22 const filters = filterString 23 .split(',') 24 .map(f => f.trim()) 25 .filter(Boolean) 26 27 // If no valid filters remain, return null 28 if (filters.length === 0) { 29 return null 30 } 31 32 // Check for mixed inclusive/exclusive filters 33 const hasExclusive = filters.some(f => f.startsWith('!')) 34 const hasInclusive = filters.some(f => !f.startsWith('!')) 35 36 if (hasExclusive && hasInclusive) { 37 // For now, we'll treat this as an error case and show all messages 38 // Log error using logForDebugging to avoid console.error lint rule 39 // We'll import and use it later when the circular dependency is resolved 40 // For now, just return null silently 41 return null 42 } 43 44 // Clean up filters (remove ! prefix) and normalize 45 const cleanFilters = filters.map(f => f.replace(/^!/, '').toLowerCase()) 46 47 return { 48 include: hasExclusive ? [] : cleanFilters, 49 exclude: hasExclusive ? cleanFilters : [], 50 isExclusive: hasExclusive, 51 } 52 }, 53) 54 55/** 56 * Extract debug categories from a message 57 * Supports multiple patterns: 58 * - "category: message" -> ["category"] 59 * - "[CATEGORY] message" -> ["category"] 60 * - "MCP server \"name\": message" -> ["mcp", "name"] 61 * - "[ANT-ONLY] 1P event: tengu_timer" -> ["ant-only", "1p"] 62 * 63 * Returns lowercase categories for case-insensitive matching 64 */ 65export function extractDebugCategories(message: string): string[] { 66 const categories: string[] = [] 67 68 // Pattern 3: MCP server "servername" - Check this first to avoid false positives 69 const mcpMatch = message.match(/^MCP server ["']([^"']+)["']/) 70 if (mcpMatch && mcpMatch[1]) { 71 categories.push('mcp') 72 categories.push(mcpMatch[1].toLowerCase()) 73 } else { 74 // Pattern 1: "category: message" (simple prefix) - only if not MCP pattern 75 const prefixMatch = message.match(/^([^:[]+):/) 76 if (prefixMatch && prefixMatch[1]) { 77 categories.push(prefixMatch[1].trim().toLowerCase()) 78 } 79 } 80 81 // Pattern 2: [CATEGORY] at the start 82 const bracketMatch = message.match(/^\[([^\]]+)]/) 83 if (bracketMatch && bracketMatch[1]) { 84 categories.push(bracketMatch[1].trim().toLowerCase()) 85 } 86 87 // Pattern 4: Check for additional categories in the message 88 // e.g., "[ANT-ONLY] 1P event: tengu_timer" should match both "ant-only" and "1p" 89 if (message.toLowerCase().includes('1p event:')) { 90 categories.push('1p') 91 } 92 93 // Pattern 5: Look for secondary categories after the first pattern 94 // e.g., "AutoUpdaterWrapper: Installation type: development" 95 const secondaryMatch = message.match( 96 /:\s*([^:]+?)(?:\s+(?:type|mode|status|event))?:/, 97 ) 98 if (secondaryMatch && secondaryMatch[1]) { 99 const secondary = secondaryMatch[1].trim().toLowerCase() 100 // Only add if it's a reasonable category name (not too long, no spaces) 101 if (secondary.length < 30 && !secondary.includes(' ')) { 102 categories.push(secondary) 103 } 104 } 105 106 // If no categories found, return empty array (uncategorized) 107 return Array.from(new Set(categories)) // Remove duplicates 108} 109 110/** 111 * Check if debug message should be shown based on filter 112 * @param categories - Categories extracted from the message 113 * @param filter - Parsed filter configuration 114 * @returns true if message should be shown 115 */ 116export function shouldShowDebugCategories( 117 categories: string[], 118 filter: DebugFilter | null, 119): boolean { 120 // No filter means show everything 121 if (!filter) { 122 return true 123 } 124 125 // If no categories found, handle based on filter mode 126 if (categories.length === 0) { 127 // In exclusive mode, uncategorized messages are excluded by default for security 128 // In inclusive mode, uncategorized messages are excluded (must match a category) 129 return false 130 } 131 132 if (filter.isExclusive) { 133 // Exclusive mode: show if none of the categories are in the exclude list 134 return !categories.some(cat => filter.exclude.includes(cat)) 135 } else { 136 // Inclusive mode: show if any of the categories are in the include list 137 return categories.some(cat => filter.include.includes(cat)) 138 } 139} 140 141/** 142 * Main function to check if a debug message should be shown 143 * Combines extraction and filtering 144 */ 145export function shouldShowDebugMessage( 146 message: string, 147 filter: DebugFilter | null, 148): boolean { 149 // Fast path: no filter means show everything 150 if (!filter) { 151 return true 152 } 153 154 // Only extract categories if we have a filter 155 const categories = extractDebugCategories(message) 156 return shouldShowDebugCategories(categories, filter) 157}