diff --git a/bin/install.js b/bin/install.js
index ebbaa4c68..869821dd0 100755
--- a/bin/install.js
+++ b/bin/install.js
@@ -710,6 +710,14 @@ function yamlQuote(value) {
return JSON.stringify(value);
}
+function yamlIdentifier(value) {
+ const text = String(value).trim();
+ if (/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(text)) {
+ return text;
+ }
+ return yamlQuote(text);
+}
+
function extractFrontmatterAndBody(content) {
if (!content.startsWith('---')) {
return { frontmatter: null, body: content };
@@ -828,7 +836,7 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
const adapter = getCursorSkillAdapterHeader(skillName);
- return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
+ return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
}
/**
@@ -845,7 +853,7 @@ function convertClaudeAgentToCursorAgent(content) {
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
const description = extractFrontmatterField(frontmatter, 'description') || '';
- const cleanFrontmatter = `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
+ const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
return `${cleanFrontmatter}\n${body}`;
}
@@ -3257,7 +3265,10 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
// Test-only exports — skip main logic when loaded as a module for testing
if (process.env.GSD_TEST_MODE) {
module.exports = {
+ yamlIdentifier,
getCodexSkillAdapterHeader,
+ convertClaudeCommandToCursorSkill,
+ convertClaudeAgentToCursorAgent,
convertClaudeToGeminiAgent,
convertClaudeAgentToCodexAgent,
generateCodexAgentToml,
diff --git a/tests/cursor-conversion.test.cjs b/tests/cursor-conversion.test.cjs
new file mode 100644
index 000000000..87652ab96
--- /dev/null
+++ b/tests/cursor-conversion.test.cjs
@@ -0,0 +1,61 @@
+/**
+ * Cursor conversion regression tests.
+ *
+ * Ensures Cursor frontmatter names are emitted as plain identifiers
+ * (without surrounding quotes), so Cursor does not treat quotes as
+ * literal parts of skill/subagent names.
+ */
+
+process.env.GSD_TEST_MODE = '1';
+
+const { describe, test } = require('node:test');
+const assert = require('node:assert');
+
+const {
+ convertClaudeCommandToCursorSkill,
+ convertClaudeAgentToCursorAgent,
+} = require('../bin/install.js');
+
+describe('convertClaudeCommandToCursorSkill', () => {
+ test('writes unquoted Cursor skill name in frontmatter', () => {
+ const input = `---
+name: quick
+description: Execute a quick task
+---
+
+
+Test body
+
+`;
+
+ const result = convertClaudeCommandToCursorSkill(input, 'gsd-quick');
+ const nameMatch = result.match(/^name:\s*(.+)$/m);
+
+ assert.ok(nameMatch, 'frontmatter contains name field');
+ assert.equal(nameMatch[1], 'gsd-quick', 'skill name is plain scalar');
+ assert.ok(!result.includes('name: "gsd-quick"'), 'quoted skill name is not emitted');
+ });
+});
+
+describe('convertClaudeAgentToCursorAgent', () => {
+ test('writes unquoted Cursor agent name in frontmatter', () => {
+ const input = `---
+name: gsd-planner
+description: Planner agent
+tools: Read, Write
+color: green
+---
+
+
+Planner body
+
+`;
+
+ const result = convertClaudeAgentToCursorAgent(input);
+ const nameMatch = result.match(/^name:\s*(.+)$/m);
+
+ assert.ok(nameMatch, 'frontmatter contains name field');
+ assert.equal(nameMatch[1], 'gsd-planner', 'agent name is plain scalar');
+ assert.ok(!result.includes('name: "gsd-planner"'), 'quoted agent name is not emitted');
+ });
+});