* Extract review content from the remote session log. * * Two producers, two event shapes: * - bughunter mode: run_hunt.sh is a SessionStart hook; its echo lands as * {type:'system', subtype:'hook_progress', stdout:'...'}. Claude never * takes a turn so there are zero assistant messages. *
(log: SDKMessage[])
| 252 | * appears once at the end of the run so reverse iteration short-circuits. |
| 253 | */ |
| 254 | function extractReviewFromLog(log: SDKMessage[]): string | null { |
| 255 | for (let i = log.length - 1; i >= 0; i--) { |
| 256 | const msg = log[i]; |
| 257 | // The final echo before hook exit may land in either the last |
| 258 | // hook_progress or the terminal hook_response depending on buffering; |
| 259 | // both have flat stdout. |
| 260 | if (msg?.type === 'system' && (msg.subtype === 'hook_progress' || msg.subtype === 'hook_response')) { |
| 261 | const tagged = extractTag(msg.stdout, REMOTE_REVIEW_TAG); |
| 262 | if (tagged?.trim()) return tagged.trim(); |
| 263 | } |
| 264 | } |
| 265 | for (let i = log.length - 1; i >= 0; i--) { |
| 266 | const msg = log[i]; |
| 267 | if (msg?.type !== 'assistant') continue; |
| 268 | const fullText = extractTextContent(msg.message.content, '\n'); |
| 269 | const tagged = extractTag(fullText, REMOTE_REVIEW_TAG); |
| 270 | if (tagged?.trim()) return tagged.trim(); |
| 271 | } |
| 272 | |
| 273 | // Hook-stdout concat fallback: a single echo should land in one event, but |
| 274 | // large JSON payloads can flush across two if the pipe buffer fills |
| 275 | // mid-write. Per-message scan above misses a tag split across events. |
| 276 | const hookStdout = log.filter(msg => msg.type === 'system' && (msg.subtype === 'hook_progress' || msg.subtype === 'hook_response')).map(msg => msg.stdout).join(''); |
| 277 | const hookTagged = extractTag(hookStdout, REMOTE_REVIEW_TAG); |
| 278 | if (hookTagged?.trim()) return hookTagged.trim(); |
| 279 | |
| 280 | // Fallback: concatenate all assistant text in chronological order. |
| 281 | const allText = log.filter((msg): msg is SDKAssistantMessage => msg.type === 'assistant').map(msg => extractTextContent(msg.message.content, '\n')).join('\n').trim(); |
| 282 | return allText || null; |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * Tag-only variant of extractReviewFromLog for delta scanning. |
no test coverage detected