From 133e881ce0f16c5dbaf43dc47f209d0aadebb697 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Sun, 12 Apr 2026 23:32:39 -0700 Subject: [PATCH] fix: install Cursor rules as mdc files --- scripts/lib/install-targets/cursor-project.js | 5 +++++ scripts/lib/install-targets/helpers.js | 18 +++++++++++++++--- tests/lib/install-targets.test.js | 16 +++++++++++----- tests/scripts/install-apply.test.js | 4 ++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/scripts/lib/install-targets/cursor-project.js b/scripts/lib/install-targets/cursor-project.js index 527ba2a6..2fd3b0ab 100644 --- a/scripts/lib/install-targets/cursor-project.js +++ b/scripts/lib/install-targets/cursor-project.js @@ -40,6 +40,11 @@ module.exports = createInstallTargetAdapter({ repoRoot, sourceRelativePath, destinationDir: path.join(targetRoot, 'rules'), + destinationNameTransform(fileName) { + return fileName.endsWith('.md') + ? `${fileName.slice(0, -3)}.mdc` + : fileName; + }, }); } diff --git a/scripts/lib/install-targets/helpers.js b/scripts/lib/install-targets/helpers.js index fd959aa7..dec17c06 100644 --- a/scripts/lib/install-targets/helpers.js +++ b/scripts/lib/install-targets/helpers.js @@ -181,7 +181,13 @@ function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePat return operations; } -function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, destinationDir }) { +function createFlatRuleOperations({ + moduleId, + repoRoot, + sourceRelativePath, + destinationDir, + destinationNameTransform, +}) { const normalizedSourcePath = normalizeRelativePath(sourceRelativePath); const sourceRoot = path.join(repoRoot || '', normalizedSourcePath); @@ -201,7 +207,10 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest if (entry.isDirectory()) { const relativeFiles = listRelativeFiles(entryPath); for (const relativeFile of relativeFiles) { - const flattenedFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`; + const defaultFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`; + const flattenedFileName = typeof destinationNameTransform === 'function' + ? destinationNameTransform(defaultFileName) + : defaultFileName; operations.push(createManagedOperation({ moduleId, sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile), @@ -210,10 +219,13 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest })); } } else if (entry.isFile()) { + const destinationFileName = typeof destinationNameTransform === 'function' + ? destinationNameTransform(entry.name) + : entry.name; operations.push(createManagedOperation({ moduleId, sourceRelativePath: path.join(normalizedSourcePath, entry.name), - destinationPath: path.join(destinationDir, entry.name), + destinationPath: path.join(destinationDir, destinationFileName), strategy: 'flatten-copy', })); } diff --git a/tests/lib/install-targets.test.js b/tests/lib/install-targets.test.js index 35c22765..45e4f7d3 100644 --- a/tests/lib/install-targets.test.js +++ b/tests/lib/install-targets.test.js @@ -103,7 +103,7 @@ function runTests() { assert.strictEqual(preserved.strategy, 'flatten-copy'); assert.strictEqual( preserved.destinationPath, - path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md') + path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.mdc') ); })) passed++; else failed++; @@ -126,16 +126,16 @@ function runTests() { assert.ok( plan.operations.some(operation => ( normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md' - && operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md') + && operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.mdc') )), - 'Should flatten common rules into namespaced files' + 'Should flatten common rules into namespaced .mdc files' ); assert.ok( plan.operations.some(operation => ( normalizedRelativePath(operation.sourceRelativePath) === 'rules/typescript/testing.md' - && operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'typescript-testing.md') + && operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'typescript-testing.mdc') )), - 'Should flatten language rules into namespaced files' + 'Should flatten language rules into namespaced .mdc files' ); assert.ok( !plan.operations.some(operation => ( @@ -143,6 +143,12 @@ function runTests() { )), 'Should not preserve nested rule directories for cursor installs' ); + assert.ok( + !plan.operations.some(operation => ( + operation.destinationPath === path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md') + )), + 'Should not emit .md Cursor rule files' + ); })) passed++; else failed++; if (test('plans antigravity remaps for workflows, skills, and flat rules', () => { diff --git a/tests/scripts/install-apply.test.js b/tests/scripts/install-apply.test.js index 06cc6cbb..f777c552 100644 --- a/tests/scripts/install-apply.test.js +++ b/tests/scripts/install-apply.test.js @@ -130,8 +130,8 @@ function runTests() { const result = run(['--target', 'cursor', 'typescript'], { cwd: projectDir, homeDir }); assert.strictEqual(result.code, 0, result.stderr); - assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.md'))); - assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'typescript-testing.md'))); + assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.mdc'))); + assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'typescript-testing.mdc'))); assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'agents', 'architect.md'))); assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md'))); assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));