Append a reasoning/thinking node (chain-of-thought from the model). These are created from the reasoning blocks captured by the OpenCode plugin. Each block represents a distinct thinking step where the agent planned its approach, evaluated alternatives, or reasoned about the codebase. The node is classified as `Decision` (the existing kind for strategic choices). Edges link from the current goal
(
&mut self,
text: &str,
duration_ms: Option<u64>,
signature: Option<&str>,
timestamp: i64,
)
| 100 | /// |
| 101 | /// Returns the new node's ID. |
| 102 | pub fn append_reasoning( |
| 103 | &mut self, |
| 104 | text: &str, |
| 105 | duration_ms: Option<u64>, |
| 106 | signature: Option<&str>, |
| 107 | timestamp: i64, |
| 108 | ) -> String { |
| 109 | // Truncate for summary: first line or first 100 chars |
| 110 | let first_line = text.lines().next().unwrap_or(text); |
| 111 | let summary = if first_line.len() > 100 { |
| 112 | let truncated: String = first_line.chars().take(97).collect(); |
| 113 | format!("{}...", truncated) |
| 114 | } else { |
| 115 | first_line.to_string() |
| 116 | }; |
| 117 | |
| 118 | let mut node = GraphNode::new(self.next_id(), NodeKind::Decision, timestamp, &summary); |
| 119 | |
| 120 | if let Some(ms) = duration_ms { |
| 121 | node = node.with_duration_ms(ms); |
| 122 | } |
| 123 | |
| 124 | // Build detail with the full reasoning text and signature |
| 125 | let mut detail = serde_json::json!({ |
| 126 | "reasoning_text": text, |
| 127 | }); |
| 128 | if let Some(ms) = duration_ms { |
| 129 | detail["reasoning_duration_ms"] = serde_json::Value::Number(ms.into()); |
| 130 | } |
| 131 | if let Some(sig) = signature { |
| 132 | detail["anthropic_signature"] = serde_json::Value::String(sig.to_string()); |
| 133 | } |
| 134 | detail["text_length"] = serde_json::Value::Number(text.len().into()); |
| 135 | node.detail = Some(detail); |
| 136 | |
| 137 | // Mark as classified so the Phase 3 consolidator doesn't touch it |
| 138 | node.classified = true; |
| 139 | node.confidence = Some(1.0); |
| 140 | |
| 141 | let node_id = node.id.clone(); |
| 142 | |
| 143 | // Edge: goal --led_to-→ reasoning (if we have a current goal) |
| 144 | if let Some(ref goal) = self.current_goal { |
| 145 | self.edges.push(GraphEdge::new( |
| 146 | goal.clone(), |
| 147 | node_id.clone(), |
| 148 | EdgeKind::LedTo, |
| 149 | )); |
| 150 | self.stats.edge_count += 1; |
| 151 | } |
| 152 | |
| 153 | // Also chain from previous node for temporal ordering |
| 154 | if let Some(ref prev) = self.last_node { |
| 155 | // Only add led_to if previous wasn't already the goal |
| 156 | if self.current_goal.as_ref() != Some(prev) { |
| 157 | self.edges.push(GraphEdge::new( |
| 158 | prev.clone(), |
| 159 | node_id.clone(), |
no test coverage detected