fix: install Cursor rules as mdc files

This commit is contained in:
Affaan Mustafa
2026-04-12 23:32:39 -07:00
parent 9da8e5f6ac
commit 133e881ce0
4 changed files with 33 additions and 10 deletions

View File

@@ -40,6 +40,11 @@ module.exports = createInstallTargetAdapter({
repoRoot, repoRoot,
sourceRelativePath, sourceRelativePath,
destinationDir: path.join(targetRoot, 'rules'), destinationDir: path.join(targetRoot, 'rules'),
destinationNameTransform(fileName) {
return fileName.endsWith('.md')
? `${fileName.slice(0, -3)}.mdc`
: fileName;
},
}); });
} }

View File

@@ -181,7 +181,13 @@ function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePat
return operations; return operations;
} }
function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, destinationDir }) { function createFlatRuleOperations({
moduleId,
repoRoot,
sourceRelativePath,
destinationDir,
destinationNameTransform,
}) {
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath); const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
const sourceRoot = path.join(repoRoot || '', normalizedSourcePath); const sourceRoot = path.join(repoRoot || '', normalizedSourcePath);
@@ -201,7 +207,10 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest
if (entry.isDirectory()) { if (entry.isDirectory()) {
const relativeFiles = listRelativeFiles(entryPath); const relativeFiles = listRelativeFiles(entryPath);
for (const relativeFile of relativeFiles) { 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({ operations.push(createManagedOperation({
moduleId, moduleId,
sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile), sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile),
@@ -210,10 +219,13 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest
})); }));
} }
} else if (entry.isFile()) { } else if (entry.isFile()) {
const destinationFileName = typeof destinationNameTransform === 'function'
? destinationNameTransform(entry.name)
: entry.name;
operations.push(createManagedOperation({ operations.push(createManagedOperation({
moduleId, moduleId,
sourceRelativePath: path.join(normalizedSourcePath, entry.name), sourceRelativePath: path.join(normalizedSourcePath, entry.name),
destinationPath: path.join(destinationDir, entry.name), destinationPath: path.join(destinationDir, destinationFileName),
strategy: 'flatten-copy', strategy: 'flatten-copy',
})); }));
} }

View File

@@ -103,7 +103,7 @@ function runTests() {
assert.strictEqual(preserved.strategy, 'flatten-copy'); assert.strictEqual(preserved.strategy, 'flatten-copy');
assert.strictEqual( assert.strictEqual(
preserved.destinationPath, preserved.destinationPath,
path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.md') path.join(projectRoot, '.cursor', 'rules', 'common-coding-style.mdc')
); );
})) passed++; else failed++; })) passed++; else failed++;
@@ -126,16 +126,16 @@ function runTests() {
assert.ok( assert.ok(
plan.operations.some(operation => ( plan.operations.some(operation => (
normalizedRelativePath(operation.sourceRelativePath) === 'rules/common/coding-style.md' 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( assert.ok(
plan.operations.some(operation => ( plan.operations.some(operation => (
normalizedRelativePath(operation.sourceRelativePath) === 'rules/typescript/testing.md' 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( assert.ok(
!plan.operations.some(operation => ( !plan.operations.some(operation => (
@@ -143,6 +143,12 @@ function runTests() {
)), )),
'Should not preserve nested rule directories for cursor installs' '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++; })) passed++; else failed++;
if (test('plans antigravity remaps for workflows, skills, and flat rules', () => { if (test('plans antigravity remaps for workflows, skills, and flat rules', () => {

View File

@@ -130,8 +130,8 @@ function runTests() {
const result = run(['--target', 'cursor', 'typescript'], { cwd: projectDir, homeDir }); const result = run(['--target', 'cursor', 'typescript'], { cwd: projectDir, homeDir });
assert.strictEqual(result.code, 0, result.stderr); 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', 'common-coding-style.mdc')));
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'typescript-testing.md'))); 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', 'agents', 'architect.md')));
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md'))); assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'commands', 'plan.md')));
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json'))); assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));