Link a goal to an intent.
(
&self,
intent_id: &str,
goal_name: &str,
)
| 1001 | |
| 1002 | /// Link a goal to an intent. |
| 1003 | pub fn vault_intent_link( |
| 1004 | &self, |
| 1005 | intent_id: &str, |
| 1006 | goal_name: &str, |
| 1007 | ) -> Result<(), RepositoryError> { |
| 1008 | let full_id = self.normalize_intent_id(intent_id)?; |
| 1009 | let intent_file = |
| 1010 | self.find_intent_path(&full_id)? |
| 1011 | .ok_or_else(|| RepositoryError::InvalidOperation { |
| 1012 | message: format!("Intent '{}' not found", full_id), |
| 1013 | })?; |
| 1014 | |
| 1015 | // Read from disk first so we pick up any user/agent edits to the |
| 1016 | // markdown body (same pattern as vault_intent_update). |
| 1017 | let disk_path = self.vault_dir().join(&intent_file); |
| 1018 | let (content_bytes, frontmatter_json) = if disk_path.exists() { |
| 1019 | let file_content = std::fs::read_to_string(&disk_path)?; |
| 1020 | let (fm_json, body) = crate::repository::vault::parse_vault_frontmatter(&file_content); |
| 1021 | (body.into_bytes(), fm_json) |
| 1022 | } else { |
| 1023 | let entry = self.vault_retrieve(&intent_file)?.ok_or_else(|| { |
| 1024 | RepositoryError::InvalidOperation { |
| 1025 | message: format!("Intent '{}' not found", full_id), |
| 1026 | } |
| 1027 | })?; |
| 1028 | (entry.content_bytes.clone(), entry.frontmatter_json.clone()) |
| 1029 | }; |
| 1030 | |
| 1031 | // Verify goal exists |
| 1032 | let goal_file = format!("goals/{}/_goal.md", goal_name); |
| 1033 | if self.vault_retrieve(&goal_file)?.is_none() { |
| 1034 | return Err(RepositoryError::InvalidOperation { |
| 1035 | message: format!("Goal '{}' not found", goal_name), |
| 1036 | }); |
| 1037 | } |
| 1038 | |
| 1039 | // Update frontmatter to add goal to the list |
| 1040 | let mut fm: serde_json::Map<String, serde_json::Value> = |
| 1041 | serde_json::from_str(&frontmatter_json).unwrap_or_default(); |
| 1042 | |
| 1043 | let goals = fm |
| 1044 | .entry("goals".to_string()) |
| 1045 | .or_insert_with(|| serde_json::Value::Array(Vec::new())); |
| 1046 | let goals = goals |
| 1047 | .as_array_mut() |
| 1048 | .ok_or_else(|| RepositoryError::InvalidOperation { |
| 1049 | message: format!("Intent '{}' has a non-array goals field", full_id), |
| 1050 | })?; |
| 1051 | let goal_val = serde_json::Value::String(goal_name.to_string()); |
| 1052 | if !goals.contains(&goal_val) { |
| 1053 | goals.push(goal_val); |
| 1054 | } |
| 1055 | let goal_count = goals.len() as u32; |
| 1056 | let new_fm = serde_json::to_string(&fm).unwrap_or_else(|_| "{}".to_string()); |
| 1057 | |
| 1058 | self.vault_store(&intent_file, VaultEntryType::Intent, content_bytes, new_fm)?; |
| 1059 | |
| 1060 | // Update manifest goal count |