diff --git a/Example/.gitignore b/Example/.gitignore index ad92b22..4397d0e 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -7,7 +7,6 @@ **/.codex/skills/ai-rules-generated-* **/.cursor/commands/ai-rules/ **/.cursor/mcp.json -**/.cursor/rules/ **/.cursor/skills/ai-rules-generated-* **/.firebender/skills/ai-rules-generated-* **/.gemini/settings.json diff --git a/README.md b/README.md index 9727bfa..af4da33 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ curl -fsSL https://raw.githubusercontent.com/block/ai-rules/main/scripts/install ai-rules generate # All agents ai-rules generate --agents claude,cursor # Specific agents ``` - Creates `CLAUDE.md`, `.cursor/rules/*.mdc`, `AGENTS.md`, etc. + Creates `CLAUDE.md`, `AGENTS.md`, and other agent-specific files. Cursor uses `AGENTS.md`. 4. **Check status** to ensure everything is in sync: ```bash diff --git a/docs/agents.md b/docs/agents.md index f4a1920..92ce885 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -9,7 +9,7 @@ | **Cline** | `.clinerules/AGENTS.md` -> inlined file | `.clinerules/AGENTS.md` -> `../ai-rules/AGENTS.md` | - | | | **Codex** | `AGENTS.md` -> inlined file | `AGENTS.md` -> `ai-rules/AGENTS.md` | - | | | **Copilot** | `AGENTS.md` -> inlined file | `AGENTS.md` -> `ai-rules/AGENTS.md` | - | | -| **Cursor** | `.cursor/rules/*.mdc` | `AGENTS.md` -> `ai-rules/AGENTS.md` | `.cursor/mcp.json` | Symlink mode: only project root level | +| **Cursor** | `AGENTS.md` -> inlined file | `AGENTS.md` -> `ai-rules/AGENTS.md` | `.cursor/mcp.json` | | | **Firebender** | `firebender.json` | `firebender.json` (references `ai-rules/AGENTS.md`) | Embedded in `firebender.json` | Supports overlay files | | **Gemini** | `GEMINI.md` -> inlined file | `GEMINI.md` -> `ai-rules/AGENTS.md` | Embedded in `.gemini/settings.json` | | | **Goose** | `AGENTS.md` -> inlined file | `AGENTS.md` -> `ai-rules/AGENTS.md` | - | | diff --git a/docs/project-structure.md b/docs/project-structure.md index c5572e4..dc77361 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -26,7 +26,6 @@ monorepo/ │ │ └── styling.md │ ├── CLAUDE.md # Symlink -> ai-rules/.generated-ai-rules/ai-rules-generated-AGENTS.md │ ├── AGENTS.md # Symlink -> ai-rules/.generated-ai-rules/ai-rules-generated-AGENTS.md -│ ├── .cursor/rules/ # Generated (*.mdc) │ └── src/ │ ├── backend/ # Backend services @@ -40,7 +39,6 @@ monorepo/ │ ├── CLAUDE.md # Symlink -> ai-rules/.generated-ai-rules/ai-rules-generated-AGENTS.md ├── AGENTS.md # Symlink -> ai-rules/.generated-ai-rules/ai-rules-generated-AGENTS.md -├── .cursor/rules/ # Root Cursor rules (generated *.mdc files) ├── .clinerules/ # Root Cline rules (symlink) ├── .mcp.json # Root MCP config (generated) └── firebender.json # Root Firebender config (generated) diff --git a/docs/rule-format.md b/docs/rule-format.md index 41c9c56..d64a982 100644 --- a/docs/rule-format.md +++ b/docs/rule-format.md @@ -28,7 +28,7 @@ All fields are optional: |-------|-------------|---------| | `description` | Context description that helps agents understand when to apply this rule if `alwaysApply` is `false` | - | | `alwaysApply` | `true` = referenced directly in agent rule files; `false` = included as optional rules based on context | `true` | -| `fileMatching` | Glob patterns for which files this rule applies to (e.g., `"**/*.ts"`, `"src/**/*.py"`). Currently supported in Cursor. | - | +| `fileMatching` | Glob patterns for which files this rule applies to (e.g., `"**/*.ts"`, `"src/**/*.py"`). Currently used for conditional rule matching in Firebender. | - | If frontmatter is omitted entirely, the file is treated as a regular markdown rule with default settings (`alwaysApply: true`). @@ -38,7 +38,7 @@ In Standard Mode, `ai-rules generate` produces a single inlined file at `ai-rule Most agent output files (e.g., `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`) are created as **symlinks** pointing to this inlined file. This ensures every agent reads the same complete content without needing `@` file expansion support. -Cursor and Firebender generate their own formats (`.mdc` files and JSON respectively) and do not use the inlined file. +Firebender generates its own JSON format and does not use the inlined file. Cursor uses the inlined `AGENTS.md` path. ## Symlink Mode diff --git a/src/agents/cursor.rs b/src/agents/cursor.rs index 60b7978..7e310a9 100644 --- a/src/agents/cursor.rs +++ b/src/agents/cursor.rs @@ -3,44 +3,55 @@ use crate::agents::external_commands_generator::ExternalCommandsGenerator; use crate::agents::external_skills_generator::ExternalSkillsGenerator; use crate::agents::mcp_generator::{ExternalMcpGenerator, McpGeneratorTrait}; use crate::agents::rule_generator::AgentRuleGenerator; +use crate::agents::single_file_based::SingleFileBasedGenerator; use crate::agents::skills_generator::SkillsGeneratorTrait; use crate::constants::{ - AGENTS_MD_FILENAME, CURSOR_COMMANDS_DIR, CURSOR_COMMANDS_SUBDIR, CURSOR_SKILLS_DIR, - GENERATED_FILE_PREFIX, MCP_JSON, + AGENTS_MD_FILENAME, CURSOR_COMMANDS_DIR, CURSOR_COMMANDS_SUBDIR, CURSOR_SKILLS_DIR, MCP_JSON, }; use crate::models::SourceFile; -use crate::utils::file_utils::{ - check_agents_md_symlink, check_directory_exact_match, create_symlink_to_agents_md, - ensure_trailing_newline, -}; use anyhow::Result; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -const MDC_EXTENSION: &str = "mdc"; +pub struct CursorGenerator { + inner: SingleFileBasedGenerator, +} -pub struct CursorGenerator; +impl CursorGenerator { + pub fn new() -> Self { + Self { + inner: SingleFileBasedGenerator::new("cursor", AGENTS_MD_FILENAME), + } + } +} + +impl Default for CursorGenerator { + fn default() -> Self { + Self::new() + } +} fn get_cursor_rules_dir(current_dir: &Path) -> PathBuf { current_dir.join(".cursor").join("rules") } +fn clean_cursor_rules_dir(current_dir: &Path) -> Result<()> { + let cursor_rules_dir = get_cursor_rules_dir(current_dir); + if cursor_rules_dir.exists() { + fs::remove_dir_all(cursor_rules_dir)?; + } + Ok(()) +} + impl AgentRuleGenerator for CursorGenerator { fn name(&self) -> &str { "cursor" } fn clean(&self, current_dir: &Path) -> Result<()> { - let cursor_rules_dir = get_cursor_rules_dir(current_dir); - if cursor_rules_dir.exists() { - fs::remove_dir_all(cursor_rules_dir)?; - } - let agent_md = current_dir.join(AGENTS_MD_FILENAME); - if agent_md.exists() && agent_md.is_symlink() { - fs::remove_file(agent_md)?; - } - Ok(()) + clean_cursor_rules_dir(current_dir)?; + self.inner.clean(current_dir) } fn generate_agent_contents( @@ -48,28 +59,8 @@ impl AgentRuleGenerator for CursorGenerator { source_files: &[SourceFile], current_dir: &Path, ) -> HashMap { - let mut agent_files = HashMap::new(); - - if source_files.is_empty() { - return agent_files; - } - - let cursor_rules_dir = get_cursor_rules_dir(current_dir); - - for source_file in source_files { - let generated_file_name = format!( - "{}{}.{}", - GENERATED_FILE_PREFIX, source_file.base_file_name, MDC_EXTENSION - ); - - let cursor_file_path = cursor_rules_dir.join(generated_file_name); - - if let Ok(content) = generate_rule_file_content(source_file) { - agent_files.insert(cursor_file_path, content); - } - } - - agent_files + self.inner + .generate_agent_contents(source_files, current_dir) } fn check_agent_contents( @@ -77,33 +68,35 @@ impl AgentRuleGenerator for CursorGenerator { source_files: &[SourceFile], current_dir: &Path, ) -> Result { - let cursor_rules_dir = get_cursor_rules_dir(current_dir); - - if source_files.is_empty() { - return Ok(!cursor_rules_dir.exists()); - } - - let expected_files = self.generate_agent_contents(source_files, current_dir); - - check_directory_exact_match(&cursor_rules_dir, &expected_files) + Ok(self.inner.check_agent_contents(source_files, current_dir)? + && !get_cursor_rules_dir(current_dir).exists()) } fn check_symlink(&self, current_dir: &Path) -> Result { - let agents_md_path = current_dir.join(AGENTS_MD_FILENAME); - check_agents_md_symlink(current_dir, &agents_md_path) + Ok(self.inner.check_symlink(current_dir)? && !get_cursor_rules_dir(current_dir).exists()) } fn gitignore_patterns(&self) -> Vec { - vec![".cursor/rules/".to_string()] + self.inner.gitignore_patterns() } fn generate_symlink(&self, current_dir: &Path) -> Result> { - let success = create_symlink_to_agents_md(current_dir, Path::new(AGENTS_MD_FILENAME))?; - if success { - Ok(vec![current_dir.join(AGENTS_MD_FILENAME)]) - } else { - Ok(vec![]) - } + clean_cursor_rules_dir(current_dir)?; + self.inner.generate_symlink(current_dir) + } + + fn uses_inlined_symlink(&self) -> bool { + true + } + + fn generate_inlined_symlink(&self, current_dir: &Path) -> Result> { + clean_cursor_rules_dir(current_dir)?; + self.inner.generate_inlined_symlink(current_dir) + } + + fn check_inlined_symlink(&self, current_dir: &Path) -> Result { + Ok(self.inner.check_inlined_symlink(current_dir)? + && !get_cursor_rules_dir(current_dir).exists()) } fn mcp_generator(&self) -> Option> { @@ -124,40 +117,12 @@ impl AgentRuleGenerator for CursorGenerator { } } -fn create_cursor_frontmatter(source_file: &SourceFile) -> String { - let globs_section = match &source_file.front_matter.file_matching_patterns { - Some(patterns) if !patterns.is_empty() => format!("globs: {}\n", patterns.join(", ")), - _ => String::new(), - }; - - format!( - "---\ndescription: {}\n{}alwaysApply: {}\n---\n\n", - source_file.front_matter.description, globs_section, source_file.front_matter.always_apply - ) -} - -fn generate_rule_file_content(source_file: &SourceFile) -> Result { - let mut cursor_content = create_cursor_frontmatter(source_file); - cursor_content.push_str(&source_file.body); - - Ok(ensure_trailing_newline(cursor_content)) -} - #[cfg(test)] mod tests { use super::*; - use crate::{models::source_file::FrontMatter, utils::test_utils::helpers::*}; + use crate::utils::test_utils::helpers::*; use tempfile::TempDir; - const EXPECTED_TEST_RULE_CONTENT: &str = r#"--- -description: Test rule -globs: **/*.ts -alwaysApply: true ---- - -test body -"#; - fn create_standard_test_source_file() -> SourceFile { create_test_source_file( "test", @@ -169,111 +134,40 @@ test body } #[test] - fn test_create_cursor_frontmatter() { - let source_file = create_standard_test_source_file(); + fn test_cursor_generator_name() { + let generator = CursorGenerator::default(); - let frontmatter = create_cursor_frontmatter(&source_file); - let expected = r#"--- -description: Test rule -globs: **/*.ts -alwaysApply: true ---- - -"#; - - assert_eq!(frontmatter, expected); + assert_eq!(generator.name(), "cursor"); } #[test] - fn test_create_cursor_frontmatter_file_matching_patterns_empty() { - let source_file = SourceFile { - base_file_name: "test".to_string(), - front_matter: FrontMatter { - description: "Test rule".to_string(), - always_apply: true, - file_matching_patterns: None, - }, - body: "test body".to_string(), - }; - - let frontmatter = create_cursor_frontmatter(&source_file); - let expected = r#"--- -description: Test rule -alwaysApply: true ---- - -"#; - - assert_eq!(frontmatter, expected); - } - - #[test] - fn test_generate_rule_file_content() { - let source_file = create_standard_test_source_file(); - - let content = generate_rule_file_content(&source_file).unwrap(); + fn test_cursor_gitignore_patterns_use_agents_md() { + let generator = CursorGenerator::default(); + let patterns = generator.gitignore_patterns(); - assert_eq!(content, EXPECTED_TEST_RULE_CONTENT); + assert_eq!(patterns, vec![AGENTS_MD_FILENAME.to_string()]); } #[test] - fn test_generate_agent_contents() { - let generator = CursorGenerator; + fn test_generate_agent_contents_uses_agents_md() { + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); - let source_files = vec![ - create_test_source_file( - "rule1", - "First rule", - true, - vec!["**/*.ts".to_string()], - "rule1 body", - ), - create_test_source_file( - "rule2", - "Second rule", - false, - vec!["**/*.js".to_string()], - "rule2 body", - ), - ]; + let source_files = vec![create_standard_test_source_file()]; let result = generator.generate_agent_contents(&source_files, temp_dir.path()); - assert_eq!(result.len(), 2); - - let expected_path1 = temp_dir - .path() - .join(".cursor/rules/ai-rules-generated-rule1.mdc"); - let expected_path2 = temp_dir - .path() - .join(".cursor/rules/ai-rules-generated-rule2.mdc"); - - let content1 = result.get(&expected_path1).unwrap(); - let expected_content1 = r#"--- -description: First rule -globs: **/*.ts -alwaysApply: true ---- - -rule1 body -"#; - assert_eq!(content1, &expected_content1); - - let content2 = result.get(&expected_path2).unwrap(); - let expected_content2 = r#"--- -description: Second rule -globs: **/*.js -alwaysApply: false ---- - -rule2 body -"#; - assert_eq!(content2, &expected_content2); + let expected_path = temp_dir.path().join(AGENTS_MD_FILENAME); + let content = result.get(&expected_path).unwrap(); + + assert_eq!( + content, + "@ai-rules/.generated-ai-rules/ai-rules-generated-test.md\n" + ); } #[test] fn test_clean_non_existing_directory() { - let generator = CursorGenerator; + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); let result = generator.clean(temp_dir.path()); @@ -283,15 +177,14 @@ rule2 body } #[test] - fn test_clean_existing_directory() { - let generator = CursorGenerator; + fn test_clean_removes_existing_directory() { + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); create_file( temp_dir.path(), ".cursor/settings.json", "existing settings content", ); - create_file( temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc", @@ -313,7 +206,7 @@ rule2 body #[test] fn test_clean_removes_agents_md_symlink() { - let generator = CursorGenerator; + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); create_file(temp_dir.path(), "ai-rules/AGENTS.md", "# Source content"); @@ -333,65 +226,87 @@ rule2 body } #[test] - fn test_check_empty_source_files_with_directory() { - let generator = CursorGenerator; + fn test_clean_removes_stale_cursor_rules_dir_and_agents_md() { + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); + create_file(temp_dir.path(), AGENTS_MD_FILENAME, "shared agents content"); create_file( temp_dir.path(), ".cursor/rules/ai-rules-generated-stale.mdc", "stale content", ); - let result = generator - .check_agent_contents(&[], temp_dir.path()) - .unwrap(); + generator.clean(temp_dir.path()).unwrap(); - assert!(!result); + assert_file_not_exists(temp_dir.path(), AGENTS_MD_FILENAME); + assert_file_not_exists(temp_dir.path(), ".cursor/rules"); } #[test] - fn test_check_with_matching_files() { - let generator = CursorGenerator; + fn test_check_empty_source_files_with_stale_cursor_rules_dir() { + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); - let source_file = create_standard_test_source_file(); create_file( temp_dir.path(), - ".cursor/rules/ai-rules-generated-test.mdc", - EXPECTED_TEST_RULE_CONTENT, + ".cursor/rules/ai-rules-generated-stale.mdc", + "stale content", ); let result = generator - .check_agent_contents(&[source_file], temp_dir.path()) + .check_agent_contents(&[], temp_dir.path()) .unwrap(); + assert!(!result); + } + + #[test] + fn test_check_symlink_with_correct_symlink() { + let temp_dir = TempDir::new().unwrap(); + let generator = CursorGenerator::default(); + + create_file(temp_dir.path(), "ai-rules/AGENTS.md", "# Source content"); + + let result = generator.generate_symlink(temp_dir.path()); + assert!(result.is_ok()); + + let result = generator.check_symlink(temp_dir.path()).unwrap(); assert!(result); } #[test] - fn test_check_with_missing_files() { - let generator = CursorGenerator; + fn test_check_symlink_rejects_stale_cursor_rules_dir() { let temp_dir = TempDir::new().unwrap(); - let source_file = create_standard_test_source_file(); + let generator = CursorGenerator::default(); - let result = generator - .check_agent_contents(&[source_file], temp_dir.path()) - .unwrap(); + create_file(temp_dir.path(), "ai-rules/AGENTS.md", "# Source content"); + generator.generate_symlink(temp_dir.path()).unwrap(); + create_file( + temp_dir.path(), + ".cursor/rules/ai-rules-generated-stale.mdc", + "stale content", + ); + let result = generator.check_symlink(temp_dir.path()).unwrap(); assert!(!result); } #[test] - fn test_check_with_incorrect_content() { - let generator = CursorGenerator; + fn test_check_agent_contents_rejects_stale_cursor_rules_dir() { let temp_dir = TempDir::new().unwrap(); + let generator = CursorGenerator::default(); let source_file = create_standard_test_source_file(); create_file( temp_dir.path(), - ".cursor/rules/ai-rules-generated-test.mdc", - "wrong content", + AGENTS_MD_FILENAME, + "@ai-rules/.generated-ai-rules/ai-rules-generated-test.md\n", + ); + create_file( + temp_dir.path(), + ".cursor/rules/ai-rules-generated-stale.mdc", + "stale content", ); let result = generator @@ -402,16 +317,23 @@ rule2 body } #[test] - fn test_check_symlink_with_correct_symlink() { - let generator = CursorGenerator; + fn test_check_inlined_symlink_rejects_stale_cursor_rules_dir() { + let generator = CursorGenerator::default(); let temp_dir = TempDir::new().unwrap(); - create_file(temp_dir.path(), "ai-rules/AGENTS.md", "# Source content"); - - let result = generator.generate_symlink(temp_dir.path()); - assert!(result.is_ok()); + create_file( + temp_dir.path(), + "ai-rules/.generated-ai-rules/ai-rules-generated-AGENTS.md", + "# Inlined content\n", + ); + generator.generate_inlined_symlink(temp_dir.path()).unwrap(); + create_file( + temp_dir.path(), + ".cursor/rules/ai-rules-generated-stale.mdc", + "stale content", + ); - let result = generator.check_symlink(temp_dir.path()).unwrap(); - assert!(result); + let result = generator.check_inlined_symlink(temp_dir.path()).unwrap(); + assert!(!result); } } diff --git a/src/agents/registry.rs b/src/agents/registry.rs index 8552831..12d5565 100644 --- a/src/agents/registry.rs +++ b/src/agents/registry.rs @@ -25,7 +25,7 @@ impl AgentToolRegistry { let generators: Vec> = vec![ claude_generator, Box::new(SingleFileBasedGenerator::new("cline", AGENTS_MD_FILENAME)), - Box::new(CursorGenerator), + Box::new(CursorGenerator::new()), Box::new(FirebenderGenerator), Box::new(SingleFileBasedGenerator::new("goose", AGENTS_MD_FILENAME)), Box::new(AmpGenerator), diff --git a/src/commands/clean.rs b/src/commands/clean.rs index 2c62ffe..169d884 100644 --- a/src/commands/clean.rs +++ b/src/commands/clean.rs @@ -217,6 +217,12 @@ Test rule content"#; ); assert!(generate_result.is_ok()); + create_file( + project_path, + ".cursor/rules/ai-rules-generated-test.mdc", + "stale cursor rules", + ); + let expected_files = [ "CLAUDE.md", ".cursor/rules/ai-rules-generated-test.mdc", diff --git a/src/commands/generate.rs b/src/commands/generate.rs index bded160..073e3a2 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -201,7 +201,7 @@ Test rule content"#; ); assert_file_exists(temp_dir.path(), "CLAUDE.md"); - assert_file_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); + assert_file_not_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); assert_file_exists(temp_dir.path(), AGENTS_MD_FILENAME); assert_file_exists(temp_dir.path(), ".gitignore"); @@ -218,18 +218,6 @@ Test rule content"#; let agents_content = std::fs::read_to_string(&agents_path).unwrap(); assert_eq!(agents_content, "# Test rule\n\nTest rule content\n"); - assert_file_content( - temp_dir.path(), - ".cursor/rules/ai-rules-generated-test.mdc", - r#"--- -description: Test rule -globs: **/*.ts -alwaysApply: true ---- - -Test rule content -"#, - ); assert_file_content( temp_dir.path(), "ai-rules/.generated-ai-rules/ai-rules-generated-test.md", @@ -239,10 +227,6 @@ Test rule content // Verify generated files have trailing newlines assert_file_has_trailing_newline(temp_dir.path(), "CLAUDE.md"); assert_file_has_trailing_newline(temp_dir.path(), AGENTS_MD_FILENAME); - assert_file_has_trailing_newline( - temp_dir.path(), - ".cursor/rules/ai-rules-generated-test.mdc", - ); assert_file_has_trailing_newline( temp_dir.path(), "ai-rules/.generated-ai-rules/ai-rules-generated-test.md", @@ -293,8 +277,8 @@ Test rule content ); assert_file_exists(temp_dir.path(), "CLAUDE.md"); - assert_file_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); - assert_file_not_exists(temp_dir.path(), AGENTS_MD_FILENAME); + assert_file_not_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); + assert_file_exists(temp_dir.path(), AGENTS_MD_FILENAME); assert_file_exists(temp_dir.path(), ".gitignore"); @@ -303,19 +287,10 @@ Test rule content assert!(claude_path.is_symlink(), "CLAUDE.md should be a symlink"); let claude_content = std::fs::read_to_string(&claude_path).unwrap(); assert_eq!(claude_content, "# Test rule\n\nTest rule content\n"); - - assert_file_content( - temp_dir.path(), - ".cursor/rules/ai-rules-generated-test.mdc", - r#"--- -description: Test rule -globs: **/*.ts -alwaysApply: true ---- - -Test rule content -"#, - ); + let agents_path = temp_dir.path().join(AGENTS_MD_FILENAME); + assert!(agents_path.is_symlink(), "AGENTS.md should be a symlink"); + let agents_content = std::fs::read_to_string(&agents_path).unwrap(); + assert_eq!(agents_content, "# Test rule\n\nTest rule content\n"); } #[test] @@ -341,7 +316,8 @@ Test rule content "project1/ai-rules/.generated-ai-rules/ai-rules-generated-rule1.md", ); assert_file_exists(temp_dir.path(), "project1/CLAUDE.md"); - assert_file_exists( + assert_file_exists(temp_dir.path(), "project1/AGENTS.md"); + assert_file_not_exists( temp_dir.path(), "project1/.cursor/rules/ai-rules-generated-rule1.mdc", ); @@ -351,7 +327,8 @@ Test rule content "project1/nested/project2/ai-rules/.generated-ai-rules/ai-rules-generated-rule2.md", ); assert_file_exists(temp_dir.path(), "project1/nested/project2/CLAUDE.md"); - assert_file_exists( + assert_file_exists(temp_dir.path(), "project1/nested/project2/AGENTS.md"); + assert_file_not_exists( temp_dir.path(), "project1/nested/project2/.cursor/rules/ai-rules-generated-rule2.mdc", ); @@ -371,7 +348,7 @@ Test rule content // Check that gitignore contains patterns with ** prefix for subdirectory matching let gitignore_content = std::fs::read_to_string(temp_dir.path().join(".gitignore")).unwrap(); - assert!(gitignore_content.contains("**/.cursor/rules/")); + assert!(!gitignore_content.contains("**/.cursor/rules/")); assert!(gitignore_content.contains("**/ai-rules/.generated-ai-rules")); assert!(gitignore_content.contains(&format!("**/{AGENTS_MD_FILENAME}"))); assert!(gitignore_content.contains("**/CLAUDE.md")); @@ -594,12 +571,7 @@ Test rule content let cursor_files = &generation_result.files_by_agent["cursor"]; assert_eq!(claude_files[0], temp_dir.path().join("CLAUDE.md")); - assert_eq!( - cursor_files[0], - temp_dir - .path() - .join(".cursor/rules/ai-rules-generated-test.mdc") - ); + assert_eq!(cursor_files[0], temp_dir.path().join(AGENTS_MD_FILENAME)); } #[test] @@ -722,7 +694,7 @@ Optional content"#, assert!(result.is_ok()); assert_file_exists(temp_dir.path(), "CLAUDE.md"); - assert_file_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); + assert_file_not_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); assert_file_exists(temp_dir.path(), AGENTS_MD_FILENAME); // Roo now uses AGENTS.md assert_file_exists(temp_dir.path(), ".mcp.json"); @@ -759,7 +731,7 @@ Optional content"#, // Agent files should be created assert_file_exists(temp_dir.path(), "CLAUDE.md"); - assert_file_exists(temp_dir.path(), ".cursor/rules/ai-rules-generated-test.mdc"); + assert_file_exists(temp_dir.path(), AGENTS_MD_FILENAME); // MCP files should NOT be created assert_file_not_exists(temp_dir.path(), ".mcp.json"); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index e2bdd54..9fc7818 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -81,8 +81,7 @@ mod tests { let generated_rule_path = "ai-rules/.generated-ai-rules/ai-rules-generated-example.md"; assert_file_exists(project_path, generated_rule_path); assert_file_exists(project_path, "CLAUDE.md"); - let cursor_rule_path = ".cursor/rules/ai-rules-generated-example.mdc"; - assert_file_exists(project_path, cursor_rule_path); + assert_file_not_exists(project_path, ".cursor/rules/ai-rules-generated-example.mdc"); assert_file_exists(project_path, AGENTS_MD_FILENAME); assert_file_exists(project_path, ".gitignore");