* 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[])
| 259 | * appears once at the end of the run so reverse iteration short-circuits. |
| 260 | */ |
| 261 | function extractReviewFromLog(log: SDKMessage[]): string | null { |
| 262 | for (let i = log.length - 1; i >= 0; i--) { |
| 263 | const msg = log[i]; |
| 264 | // The final echo before hook exit may land in either the last |
| 265 | // hook_progress or the terminal hook_response depending on buffering; |
| 266 | // both have flat stdout. |
| 267 | if (msg?.type === 'system' && ((msg as SDKSystemMessageWithFields).subtype === 'hook_progress' || (msg as SDKSystemMessageWithFields).subtype === 'hook_response')) { |
| 268 | const tagged = extractTag((msg as SDKSystemMessageWithFields).stdout, REMOTE_REVIEW_TAG); |
| 269 | if (tagged?.trim()) return tagged.trim(); |
| 270 | } |
| 271 | } |
| 272 | for (let i = log.length - 1; i >= 0; i--) { |
| 273 | const msg = log[i]; |
| 274 | if (msg?.type !== 'assistant') continue; |
| 275 | const fullText = extractTextContent((msg as unknown as SDKMessageWithMessage).message.content, '\n'); |
| 276 | const tagged = extractTag(fullText, REMOTE_REVIEW_TAG); |
| 277 | if (tagged?.trim()) return tagged.trim(); |
| 278 | } |
| 279 | |
| 280 | // Hook-stdout concat fallback: a single echo should land in one event, but |
| 281 | // large JSON payloads can flush across two if the pipe buffer fills |
| 282 | // mid-write. Per-message scan above misses a tag split across events. |
| 283 | const hookStdout = log.filter(msg => msg.type === 'system' && ((msg as SDKSystemMessageWithFields).subtype === 'hook_progress' || (msg as SDKSystemMessageWithFields).subtype === 'hook_response')).map(msg => (msg as SDKSystemMessageWithFields).stdout).join(''); |
| 284 | const hookTagged = extractTag(hookStdout, REMOTE_REVIEW_TAG); |
| 285 | if (hookTagged?.trim()) return hookTagged.trim(); |
| 286 | |
| 287 | // Fallback: concatenate all assistant text in chronological order. |
| 288 | const allText = log.filter((msg): msg is SDKAssistantMessage => msg.type === 'assistant').map(msg => extractTextContent((msg as unknown as SDKMessageWithMessage).message.content, '\n')).join('\n').trim(); |
| 289 | return allText || null; |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * Tag-only variant of extractReviewFromLog for delta scanning. |
no test coverage detected