Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.5 kB · 142 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143/** * Command semantics configuration for interpreting exit codes in PowerShell. * * PowerShell-native cmdlets do NOT need exit-code semantics: * - Select-String (grep equivalent) exits 0 on no-match (returns $null) * - Compare-Object (diff equivalent) exits 0 regardless * - Test-Path exits 0 regardless (returns bool via pipeline) * Native cmdlets signal failure via terminating errors ($?), not exit codes. * * However, EXTERNAL executables invoked from PowerShell DO set $LASTEXITCODE, * and many use non-zero codes to convey information rather than failure: * - grep.exe / rg.exe (Git for Windows, scoop, etc.): 1 = no match * - findstr.exe (Windows native): 1 = no match * - robocopy.exe (Windows native): 0-7 = success, 8+ = error (notorious!) * * Without this module, PowerShellTool throws ShellError on any non-zero exit, * so `robocopy` reporting "files copied successfully" (exit 1) shows as an error. */
export type CommandSemantic = ( exitCode: number, stdout: string, stderr: string,) => { isError: boolean message?: string}
/** * Default semantic: treat only 0 as success, everything else as error */const DEFAULT_SEMANTIC: CommandSemantic = (exitCode, _stdout, _stderr) => ({ isError: exitCode !== 0, message: exitCode !== 0 ? `Command failed with exit code ${exitCode}` : undefined,})
/** * grep / ripgrep: 0 = matches found, 1 = no matches, 2+ = error */const GREP_SEMANTIC: CommandSemantic = (exitCode, _stdout, _stderr) => ({ isError: exitCode >= 2, message: exitCode === 1 ? 'No matches found' : undefined,})
/** * Command-specific semantics for external executables. * Keys are lowercase command names WITHOUT .exe suffix. * * Deliberately omitted: * - 'diff': Ambiguous. Windows PowerShell 5.1 aliases `diff` → Compare-Object * (exit 0 on differ), but PS Core / Git for Windows may resolve to diff.exe * (exit 1 on differ). Cannot reliably interpret. * - 'fc': Ambiguous. PowerShell aliases `fc` → Format-Custom (a native cmdlet), * but `fc.exe` is the Windows file compare utility (exit 1 = files differ). * Same aliasing problem as `diff`. * - 'find': Ambiguous. Windows find.exe (text search) vs Unix find.exe * (file search via Git for Windows) have different semantics. * - 'test', '[': Not PowerShell constructs. * - 'select-string', 'compare-object', 'test-path': Native cmdlets exit 0. */const COMMAND_SEMANTICS: Map<string, CommandSemantic> = new Map([ // External grep/ripgrep (Git for Windows, scoop, choco) ['grep', GREP_SEMANTIC], ['rg', GREP_SEMANTIC],
// findstr.exe: Windows native text search // 0 = match found, 1 = no match, 2 = error ['findstr', GREP_SEMANTIC],
// robocopy.exe: Windows native robust file copy // Exit codes are a BITFIELD — 0-7 are success, 8+ indicates at least one failure: // 0 = no files copied, no mismatch, no failures (already in sync) // 1 = files copied successfully // 2 = extra files/dirs detected (no copy) // 4 = mismatched files/dirs detected // 8 = some files/dirs could not be copied (copy errors) // 16 = serious error (robocopy did not copy any files) // This is the single most common "CI failed but nothing's wrong" Windows gotcha. [ 'robocopy', (exitCode, _stdout, _stderr) => ({ isError: exitCode >= 8, message: exitCode === 0 ? 'No files copied (already in sync)' : exitCode >= 1 && exitCode < 8 ? exitCode & 1 ? 'Files copied successfully' : 'Robocopy completed (no errors)' : undefined, }), ],])
/** * Extract the command name from a single pipeline segment. * Strips leading `&` / `.` call operators and `.exe` suffix, lowercases. */function extractBaseCommand(segment: string): string { // Strip PowerShell call operators: & "cmd", . "cmd" // (& and . at segment start followed by whitespace invoke the next token) const stripped = segment.trim().replace(/^[&.]\s+/, '') const firstToken = stripped.split(/\s+/)[0] || '' // Strip surrounding quotes if command was invoked as & "grep.exe" const unquoted = firstToken.replace(/^["']|["']$/g, '') // Strip path: C:\bin\grep.exe → grep.exe, .\rg.exe → rg.exe const basename = unquoted.split(/[\\/]/).pop() || unquoted // Strip .exe suffix (Windows is case-insensitive) return basename.toLowerCase().replace(/\.exe$/, '')}
/** * Extract the primary command from a PowerShell command line. * Takes the LAST pipeline segment since that determines the exit code. * * Heuristic split on `;` and `|` — may get it wrong for quoted strings or * complex constructs. Do NOT depend on this for security; it's only used * for exit-code interpretation (false negatives just fall back to default). */function heuristicallyExtractBaseCommand(command: string): string { const segments = command.split(/[;|]/).filter(s => s.trim()) const last = segments[segments.length - 1] || command return extractBaseCommand(last)}
/** * Interpret command result based on semantic rules */export function interpretCommandResult( command: string, exitCode: number, stdout: string, stderr: string,): { isError: boolean message?: string} { const baseCommand = heuristicallyExtractBaseCommand(command) const semantic = COMMAND_SEMANTICS.get(baseCommand) ?? DEFAULT_SEMANTIC return semantic(exitCode, stdout, stderr)}