Build the unhashed turn data (transcript + reasoning) from the agent's transcript file and the recorded change's FileOps. Returns `None` if the transcript is not available, empty, or unparseable. Reasoning generation failure is logged and skipped (non-fatal).
(
options: &TurnRecordOptions<'_>,
recorded_files: &[String],
_outcome: &atomic_repository::record::RecordOutcome,
)
| 251 | /// Returns `None` if the transcript is not available, empty, or unparseable. |
| 252 | /// Reasoning generation failure is logged and skipped (non-fatal). |
| 253 | pub(crate) fn build_unhashed_turn_data( |
| 254 | options: &TurnRecordOptions<'_>, |
| 255 | recorded_files: &[String], |
| 256 | _outcome: &atomic_repository::record::RecordOutcome, |
| 257 | ) -> Option<transcript::UnhashedTurnData> { |
| 258 | // Check if we have a transcript file |
| 259 | let transcript_path = options.session.transcript_path.as_ref()?; |
| 260 | if !transcript_path.exists() { |
| 261 | log::debug!( |
| 262 | "Transcript file not found at {} — skipping", |
| 263 | transcript_path.display(), |
| 264 | ); |
| 265 | return None; |
| 266 | } |
| 267 | |
| 268 | // Read raw transcript |
| 269 | let raw = match std::fs::read(transcript_path) { |
| 270 | Ok(data) => data, |
| 271 | Err(e) => { |
| 272 | log::warn!( |
| 273 | "Failed to read transcript at {}: {}", |
| 274 | transcript_path.display(), |
| 275 | e |
| 276 | ); |
| 277 | return None; |
| 278 | } |
| 279 | }; |
| 280 | |
| 281 | if raw.is_empty() { |
| 282 | return None; |
| 283 | } |
| 284 | |
| 285 | // Determine format from agent name |
| 286 | let format = transcript::format_for_agent(&options.session.agent_name); |
| 287 | |
| 288 | // Condense into structured entries |
| 289 | let entries = transcript::condense_transcript(&raw, format); |
| 290 | if entries.is_empty() { |
| 291 | log::debug!("Condensed transcript is empty — skipping"); |
| 292 | return None; |
| 293 | } |
| 294 | |
| 295 | // Build the base unhashed data |
| 296 | let data = transcript::UnhashedTurnData::new( |
| 297 | &options.session.session_id, |
| 298 | options.turn_number, |
| 299 | format, |
| 300 | entries, |
| 301 | recorded_files, |
| 302 | ); |
| 303 | |
| 304 | // Reasoning generation is NOT done here — it's too slow for the hook |
| 305 | // hot path (calls Claude CLI, 30+ seconds) and potentially recursive |
| 306 | // when running inside a Claude Code hook. |
| 307 | // |
| 308 | // Use `atomic agent explain` to generate reasoning on demand after |
| 309 | // the turn is recorded. That command reads the change's unhashed |
| 310 | // transcript, calls Claude CLI, anchors learnings to the CRDT graph, |
no test coverage detected