diff --git a/src-tauri/src/services/command_writer.rs b/src-tauri/src/services/command_writer.rs index d5cd00c..cecf757 100644 --- a/src-tauri/src/services/command_writer.rs +++ b/src-tauri/src/services/command_writer.rs @@ -41,6 +41,12 @@ pub(crate) fn generate_command_markdown(command: &Command) -> String { } } + if let Some(ref tags) = command.tags { + if !tags.is_empty() { + frontmatter.push_str(&format!("tags: {}\n", serde_json::to_string(tags).unwrap())); + } + } + frontmatter.push_str("---\n\n"); format!("{}{}", frontmatter, command.content) } @@ -267,6 +273,31 @@ mod tests { assert!(md.contains("Minimal content.")); } + #[test] + fn test_generate_command_markdown_emits_tags_as_json_array() { + // Mirrors the rule_writer fix: `tags` is read from the DB via + // `serde_json::from_str`, so if a scanner ever ingests a command + // frontmatter the value must be valid JSON, not comma-joined. + // Also pins against silent-drop: previously `command.tags` was not + // written to frontmatter at all. + let mut command = sample_minimal_command(); + command.tags = Some(vec!["triage".to_string(), "prs".to_string()]); + + let md = generate_command_markdown(&command); + + assert!(md.contains("tags: [\"triage\",\"prs\"]\n")); + } + + #[test] + fn test_generate_command_markdown_omits_empty_tags() { + let mut command = sample_minimal_command(); + command.tags = Some(vec![]); + + let md = generate_command_markdown(&command); + + assert!(!md.contains("tags:")); + } + // ========================================================================= // write_command_file tests // ========================================================================= diff --git a/src-tauri/src/services/skill_writer.rs b/src-tauri/src/services/skill_writer.rs index a6a3f24..be3a268 100644 --- a/src-tauri/src/services/skill_writer.rs +++ b/src-tauri/src/services/skill_writer.rs @@ -68,6 +68,12 @@ pub(crate) fn generate_skill_markdown(skill: &Skill) -> String { } } + if let Some(ref tags) = skill.tags { + if !tags.is_empty() { + frontmatter.push_str(&format!("tags: {}\n", serde_json::to_string(tags).unwrap())); + } + } + frontmatter.push_str("---\n\n"); format!("{}{}", frontmatter, skill.content) } @@ -271,6 +277,31 @@ mod tests { assert!(md.contains("name: minimal\n")); } + #[test] + fn test_generate_skill_markdown_emits_tags_as_json_array() { + // Mirrors the rule_writer fix: `tags` is read from the DB via + // `serde_json::from_str`, so if a scanner ever ingests a skill + // frontmatter the value must be valid JSON, not comma-joined. + // Also pins against silent-drop: previously `skill.tags` was not + // written to frontmatter at all. + let mut skill = sample_minimal_skill(); + skill.tags = Some(vec!["refactor".to_string(), "typescript".to_string()]); + + let md = generate_skill_markdown(&skill); + + assert!(md.contains("tags: [\"refactor\",\"typescript\"]\n")); + } + + #[test] + fn test_generate_skill_markdown_omits_empty_tags() { + let mut skill = sample_minimal_skill(); + skill.tags = Some(vec![]); + + let md = generate_skill_markdown(&skill); + + assert!(!md.contains("tags:")); + } + // ========================================================================= // write_skill_file tests (file system) // ========================================================================= diff --git a/src-tauri/src/services/subagent_writer.rs b/src-tauri/src/services/subagent_writer.rs index 35edecb..ed1880c 100644 --- a/src-tauri/src/services/subagent_writer.rs +++ b/src-tauri/src/services/subagent_writer.rs @@ -78,6 +78,12 @@ pub(crate) fn generate_subagent_markdown(subagent: &SubAgent) -> String { } } + if let Some(ref tags) = subagent.tags { + if !tags.is_empty() { + frontmatter.push_str(&format!("tags: {}\n", serde_json::to_string(tags).unwrap())); + } + } + frontmatter.push_str("---\n\n"); format!("{}{}", frontmatter, subagent.content) } @@ -320,6 +326,31 @@ mod tests { assert!(md.contains("---\n\nYou are a helpful assistant.")); } + #[test] + fn test_generate_subagent_markdown_emits_tags_as_json_array() { + // Mirrors the rule_writer fix: `tags` is read from the DB via + // `serde_json::from_str`, so if a scanner ever ingests a subagent + // frontmatter the value must be valid JSON, not comma-joined. + // Also pins against silent-drop: previously `subagent.tags` was not + // written to frontmatter at all. + let mut subagent = sample_minimal_subagent(); + subagent.tags = Some(vec!["review".to_string(), "quality".to_string()]); + + let md = generate_subagent_markdown(&subagent); + + assert!(md.contains("tags: [\"review\",\"quality\"]\n")); + } + + #[test] + fn test_generate_subagent_markdown_omits_empty_tags() { + let mut subagent = sample_minimal_subagent(); + subagent.tags = Some(vec![]); + + let md = generate_subagent_markdown(&subagent); + + assert!(!md.contains("tags:")); + } + #[test] fn test_generate_subagent_markdown_empty_tools_skipped() { let mut subagent = sample_full_subagent();