source dump of claude code
at main 140 lines 3.7 kB view raw
1/** 2 * Command semantics configuration for interpreting exit codes in different contexts. 3 * 4 * Many commands use exit codes to convey information other than just success/failure. 5 * For example, grep returns 1 when no matches are found, which is not an error condition. 6 */ 7 8import { splitCommand_DEPRECATED } from '../../utils/bash/commands.js' 9 10export type CommandSemantic = ( 11 exitCode: number, 12 stdout: string, 13 stderr: string, 14) => { 15 isError: boolean 16 message?: string 17} 18 19/** 20 * Default semantic: treat only 0 as success, everything else as error 21 */ 22const DEFAULT_SEMANTIC: CommandSemantic = (exitCode, _stdout, _stderr) => ({ 23 isError: exitCode !== 0, 24 message: 25 exitCode !== 0 ? `Command failed with exit code ${exitCode}` : undefined, 26}) 27 28/** 29 * Command-specific semantics 30 */ 31const COMMAND_SEMANTICS: Map<string, CommandSemantic> = new Map([ 32 // grep: 0=matches found, 1=no matches, 2+=error 33 [ 34 'grep', 35 (exitCode, _stdout, _stderr) => ({ 36 isError: exitCode >= 2, 37 message: exitCode === 1 ? 'No matches found' : undefined, 38 }), 39 ], 40 41 // ripgrep has same semantics as grep 42 [ 43 'rg', 44 (exitCode, _stdout, _stderr) => ({ 45 isError: exitCode >= 2, 46 message: exitCode === 1 ? 'No matches found' : undefined, 47 }), 48 ], 49 50 // find: 0=success, 1=partial success (some dirs inaccessible), 2+=error 51 [ 52 'find', 53 (exitCode, _stdout, _stderr) => ({ 54 isError: exitCode >= 2, 55 message: 56 exitCode === 1 ? 'Some directories were inaccessible' : undefined, 57 }), 58 ], 59 60 // diff: 0=no differences, 1=differences found, 2+=error 61 [ 62 'diff', 63 (exitCode, _stdout, _stderr) => ({ 64 isError: exitCode >= 2, 65 message: exitCode === 1 ? 'Files differ' : undefined, 66 }), 67 ], 68 69 // test/[: 0=condition true, 1=condition false, 2+=error 70 [ 71 'test', 72 (exitCode, _stdout, _stderr) => ({ 73 isError: exitCode >= 2, 74 message: exitCode === 1 ? 'Condition is false' : undefined, 75 }), 76 ], 77 78 // [ is an alias for test 79 [ 80 '[', 81 (exitCode, _stdout, _stderr) => ({ 82 isError: exitCode >= 2, 83 message: exitCode === 1 ? 'Condition is false' : undefined, 84 }), 85 ], 86 87 // wc, head, tail, cat, etc.: these typically only fail on real errors 88 // so we use default semantics 89]) 90 91/** 92 * Get the semantic interpretation for a command 93 */ 94function getCommandSemantic(command: string): CommandSemantic { 95 // Extract the base command (first word, handling pipes) 96 const baseCommand = heuristicallyExtractBaseCommand(command) 97 const semantic = COMMAND_SEMANTICS.get(baseCommand) 98 return semantic !== undefined ? semantic : DEFAULT_SEMANTIC 99} 100 101/** 102 * Extract just the command name (first word) from a single command string. 103 */ 104function extractBaseCommand(command: string): string { 105 return command.trim().split(/\s+/)[0] || '' 106} 107 108/** 109 * Extract the primary command from a complex command line; 110 * May get it super wrong - don't depend on this for security 111 */ 112function heuristicallyExtractBaseCommand(command: string): string { 113 const segments = splitCommand_DEPRECATED(command) 114 115 // Take the last command as that's what determines the exit code 116 const lastCommand = segments[segments.length - 1] || command 117 118 return extractBaseCommand(lastCommand) 119} 120 121/** 122 * Interpret command result based on semantic rules 123 */ 124export function interpretCommandResult( 125 command: string, 126 exitCode: number, 127 stdout: string, 128 stderr: string, 129): { 130 isError: boolean 131 message?: string 132} { 133 const semantic = getCommandSemantic(command) 134 const result = semantic(exitCode, stdout, stderr) 135 136 return { 137 isError: result.isError, 138 message: result.message, 139 } 140}