Format the change ledger (causal decision DAG) for display. Displays the structured provenance graph stored in the `.provenance` file: goals, tool executions, explorations, commitments, and patch proposals that led to this change.
(&self, change_hash: &Hash, repo: &Repository)
| 577 | /// file: goals, tool executions, explorations, commitments, and patch |
| 578 | /// proposals that led to this change. |
| 579 | fn format_change_ledger(&self, change_hash: &Hash, repo: &Repository) -> String { |
| 580 | let mut output = String::new(); |
| 581 | |
| 582 | let graphs = match repo.find_provenance_for_change(change_hash) { |
| 583 | Ok(g) if !g.is_empty() => g, |
| 584 | Ok(_) => { |
| 585 | output.push_str(&format!( |
| 586 | "{}\n", |
| 587 | hint("No provenance graph found for this change.") |
| 588 | )); |
| 589 | return output; |
| 590 | } |
| 591 | Err(e) => { |
| 592 | output.push_str(&format!( |
| 593 | "{}\n", |
| 594 | hint(&format!("Failed to load provenance: {}", e)) |
| 595 | )); |
| 596 | return output; |
| 597 | } |
| 598 | }; |
| 599 | |
| 600 | for (_graph_hash, graph) in &graphs { |
| 601 | output.push_str(&format!("{}\n", emphasis("=== Change Ledger ==="))); |
| 602 | output.push_str(&format!(" Session: {}\n", info(&graph.session_id))); |
| 603 | output.push_str(&format!( |
| 604 | " Agent: {} ({})\n", |
| 605 | info(&graph.agent_display_name), |
| 606 | hint(&graph.agent_vendor) |
| 607 | )); |
| 608 | output.push_str(&format!( |
| 609 | " Nodes: {} Edges: {} Changes: {}\n", |
| 610 | graph.node_count(), |
| 611 | graph.edge_count(), |
| 612 | graph.change_count() |
| 613 | )); |
| 614 | output.push('\n'); |
| 615 | |
| 616 | // Display nodes |
| 617 | for node in &graph.nodes { |
| 618 | let kind_str = format!("{}", node.kind); |
| 619 | let kind_styled = format!("{}", style(&kind_str).bold().cyan()); |
| 620 | let duration = node |
| 621 | .duration_ms |
| 622 | .map(|ms| format!(" ({}ms)", ms)) |
| 623 | .unwrap_or_default(); |
| 624 | let tool = node.tool_name.as_deref().unwrap_or(""); |
| 625 | let tool_str = if tool.is_empty() { |
| 626 | String::new() |
| 627 | } else { |
| 628 | format!(" {}", hint(&format!("[{}]", tool))) |
| 629 | }; |
| 630 | |
| 631 | // Extract a one-line detail string from the structured detail JSON |
| 632 | let detail_str = node |
| 633 | .detail |
| 634 | .as_ref() |
| 635 | .and_then(|d| format_node_detail(d)) |
| 636 | .map(|s| format!("\n {}", hint(&s))) |
no test coverage detected