feat(#785): write .cursor/commands/ Cursor 1.6 slash-command surface (#805)

* feat(#785): write .cursor/commands/ as Cursor 1.6 slash-command surface

Cursor 1.6 (released 2025-09-12) introduced plain-markdown slash commands
in `.cursor/commands/<name>.md` — no frontmatter, invocable via `/` in the
Agent input. GSD previously emitted only `~/.cursor/skills/` for Cursor.

This PR wires a second artifact kind for `cursor` in
`runtime-artifact-layout.cts`: `convertedCommandsKind('commands', 'gsd-',
'convertClaudeCommandToCursorCommand', configDir)`. The new kind applies the
same `convertClaudeToCursorMarkdown` transforms (tool renames, brand
substitution, slash-command normalisation) and then strips YAML frontmatter
so the output is plain prose. Skills output is unchanged.

`stageCommandsForRuntimeFlat` in `install-profiles.cts` stages each source
`.md` as a flat `<stem>.md` in a temp dir; the existing `_copyStaged` commands
path then prefixes and copies to `<configDir>/commands/`.

`.cursor/mcp.json` is explicitly OUT OF SCOPE: GSD ships no MCP server; the
`mcpServers` schema cannot be usefully populated by the installer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(#785): address review nit

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-07 15:07:14 -04:00
committed by GitHub
parent c480e04188
commit 74a818308e
8 changed files with 309 additions and 13 deletions

View File

@@ -2196,6 +2196,29 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
}
/**
* Convert a Claude Code command to a Cursor 1.6 slash command (#785).
*
* Cursor slash commands live in `.cursor/commands/<name>.md` and are
* plain markdown — no YAML frontmatter, no adapter header. The filename
* becomes the command name (e.g. `gsd-help.md` → `/gsd-help`).
*
* Applies the same `convertClaudeToCursorMarkdown` transforms as the skill
* converter (tool renames, brand substitution, slash-command normalisation),
* then strips the YAML frontmatter block so only the prose body remains.
*
* @param {string} content raw Claude Code command markdown (may have frontmatter)
* @param {string} _commandName the target command name (unused; present for
* API symmetry with other converters so the runtime-artifact-layout stage
* function can call it uniformly)
* @returns {string} plain markdown body, no frontmatter
*/
function convertClaudeCommandToCursorCommand(content, _commandName) {
const converted = convertClaudeToCursorMarkdown(content);
const { body } = extractFrontmatterAndBody(converted);
return body.trimStart();
}
/**
* Convert Claude Code agent markdown to Cursor agent format.
* Strips frontmatter fields Cursor doesn't support (color, skills),
@@ -8603,6 +8626,22 @@ function install(isGlobal, runtime = 'claude', options = {}) {
} else {
failures.push('skills/gsd-*');
}
// Cursor only: also report the commands/ output (#785 — Cursor 1.6 slash commands)
if (isCursor) {
const commandsDir = path.join(targetDir, 'commands');
if (fs.existsSync(commandsDir)) {
const cmdCount = fs.readdirSync(commandsDir)
.filter(f => f.startsWith('gsd-') && f.endsWith('.md')).length;
if (cmdCount > 0) {
console.log(` ${green}✓${reset} Installed ${cmdCount} slash commands to commands/`);
} else {
failures.push('commands/gsd-*');
}
} else {
failures.push('commands/gsd-*');
}
}
}
} else if (isOpencode || isKilo) {
// OpenCode/Kilo: flat structure in command/ directory
@@ -10895,6 +10934,7 @@ module.exports = {
computePathPrefix,
getCodexSkillAdapterHeader,
convertClaudeCommandToCursorSkill,
convertClaudeCommandToCursorCommand,
convertClaudeAgentToCursorAgent,
convertClaudeToGeminiMarkdown,
convertSlashCommandsToGeminiMentions,