()
| 9 | const path = require('path'); |
| 10 | const { normalizeCodexHookInput } = require('../runtimes/codex/adapters/hook-input'); |
| 11 | const { toLegacyHookPayload } = require('../core/hooks/hook-context'); |
| 12 | |
| 13 | const SECURITY_HOOKS = new Set(['protect-files', 'external-action-gate']); |
| 14 | |
| 15 | function isSecurityHook(hookName) { |
| 16 | return SECURITY_HOOKS.has(hookName); |
| 17 | } |
| 18 | |
| 19 | function failureResult(hookName, reason, error = null) { |
| 20 | const blocks = isSecurityHook(hookName); |
| 21 | const detail = error?.message ? `: ${error.message}` : ''; |
| 22 | return { |
| 23 | status: blocks ? 2 : 0, |
| 24 | stdout: '', |
| 25 | stderr: `[codex-adapter] ${hookName || 'unknown hook'} ${reason}${detail}\n`, |
| 26 | nativeEventName: null, |
| 27 | }; |
| 28 | } |
| 29 | |
| 30 | function parseApplyPatchOperations(command) { |
| 31 | if (typeof command !== 'string' || !command.trim()) { |
| 32 | throw new TypeError('apply_patch tool_input.command must be a non-empty string'); |
| 33 | } |
| 34 | if (!/^\*\*\* Begin Patch\s*$/m.test(command) || !/^\*\*\* End Patch\s*$/m.test(command)) { |
| 35 | throw new TypeError('apply_patch command is missing patch boundaries'); |
| 36 | } |
| 37 | |
| 38 | const operations = []; |
| 39 | const seen = new Set(); |
| 40 | for (const line of command.split(/\r?\n/)) { |
| 41 | const fileMatch = /^\*\*\* (Add|Update|Delete) File:\s*(.+?)\s*$/.exec(line); |
| 42 | const moveMatch = /^\*\*\* Move to:\s*(.+?)\s*$/.exec(line); |
| 43 | const match = fileMatch || moveMatch; |
| 44 | if (!match) continue; |
| 45 | let target = (fileMatch ? fileMatch[2] : moveMatch[1]).trim(); |
| 46 | if ((target.startsWith('"') && target.endsWith('"')) |
| 47 | || (target.startsWith("'") && target.endsWith("'"))) { |
| 48 | target = target.slice(1, -1); |
| 49 | } |
| 50 | if (!target) throw new TypeError('apply_patch contains an empty target path'); |
| 51 | const toolName = fileMatch?.[1] === 'Add' || moveMatch ? 'Write' : 'Edit'; |
| 52 | const signature = `${toolName}\0${target}`; |
| 53 | if (!seen.has(signature)) { |
| 54 | seen.add(signature); |
| 55 | operations.push({ filePath: target, toolName }); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | if (operations.length === 0) { |
| 60 | throw new TypeError('apply_patch command contains no target paths'); |
| 61 | } |
| 62 | return operations; |
no test coverage detected