(output: string)
| 34 | * null when neither is present. Best-effort — malformed JSON yields null, not a throw. |
| 35 | */ |
| 36 | export function parseReceipt(output: string): RunReceipt | null { |
| 37 | const fenced = /```(?:qodex-receipt)\s*\n([\s\S]*?)\n```/i.exec(output) |
| 38 | ?? /~~~(?:qodex-receipt)\s*\n([\s\S]*?)\n~~~/i.exec(output); |
| 39 | if (fenced) { |
| 40 | try { |
| 41 | const obj = JSON.parse(fenced[1]!.trim()); |
| 42 | const coerced = coerce(obj); |
| 43 | if (coerced) return coerced; |
| 44 | } catch { /* fall through to the headline */ } |
| 45 | } |
| 46 | // Headline fallback (also the recipe's human-facing status line). |
| 47 | const opened = /VERIFIED-PR:\s*opened\s+(\S+)/i.exec(output); |
| 48 | if (opened) return { status: 'opened', prUrl: opened[1] }; |
| 49 | const blocked = /VERIFIED-PR:\s*blocked\s*[—:-]+\s*(.+)/i.exec(output); |
| 50 | if (blocked) return { status: 'blocked', reason: blocked[1]!.trim() }; |
| 51 | return null; |
| 52 | } |
| 53 | |
| 54 | function coerce(o: any): RunReceipt | null { |
| 55 | if (!o || typeof o !== 'object') return null; |
no test coverage detected