fix(#2341): de-dup Cursor / menu by marking skills user-invocable:false (#2386)

Cursor installs both a skills and a commands surface and shows both in '/', duplicating every /gsd-*. Extend the #789 CodeBuddy de-dup to Cursor: convertClaudeCommandToCursorSkill (in both src and the live bin/install.js) now emits user-invocable:false, so the skill stays model-invocable while the commands surface is the single '/' entry point.

Closes #2341. Admin-merged (self-review bypass) with full green CI.
This commit is contained in:
Tom Boucher
2026-07-17 15:20:19 -04:00
committed by GitHub
parent e6c16efa6d
commit 58028eaf56
6 changed files with 143 additions and 74 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 2386
---
**Cursor no longer shows every `/gsd-*` command twice** — a `--cursor` install wrote both a skill and a slash command for each action, so every GSD entry appeared twice in Cursor's `/` menu. GSD now installs Cursor skills as `user-invocable: false` (matching the existing CodeBuddy behavior), so the slash command is the single `/` entry point while skills remain model-invocable. (#2341)

View File

@@ -2565,7 +2565,13 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
const adapter = getCursorSkillAdapterHeader(skillName);
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
// #2341: mark user-invocable:false so the skill is NOT shown in Cursor's '/'
// menu (it defaults to true). Cursor also writes a commands/ surface (#785),
// and surfacing both duplicated every /gsd-* entry. This mirrors the #789
// CodeBuddy de-dup: the commands/ surface is the sole '/' entry point; skills
// stay model-invocable background knowledge. (user-invocable:false hides from
// '/' while keeping model invocation — distinct from disable-model-invocation.)
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\nuser-invocable: false\n---\n\n${adapter}\n\n${body.trimStart()}`;
}
/**

View File

@@ -276,7 +276,7 @@ COPILOT_CONFIG_DIR=~/.copilot-alt npx @opengsd/gsd-core@latest --copilot --globa
npx @opengsd/gsd-core@latest --cursor --global
```
Skills land in `~/.cursor/`. GSD installs skills, agents, and rule references.
Artifacts land in `~/.cursor/`. GSD installs slash commands (`~/.cursor/commands/gsd-*.md`), skills (`~/.cursor/skills/gsd-*/SKILL.md`), agents, and rule references. Each GSD action appears once in Cursor's `/` menu: the command surface is the single `/` entry point, and the skills are installed with `user-invocable: false` so they stay model-invocable background knowledge without duplicating the `/` entries.
**Override the install directory:**

View File

@@ -954,7 +954,13 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
const adapter = getCursorSkillAdapterHeader(skillName);
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
// #2341: mark user-invocable:false so the skill is NOT shown in Cursor's '/'
// menu (it defaults to true). Cursor also writes a commands/ surface (#785),
// and surfacing both duplicated every /gsd-* entry. This mirrors the #789
// CodeBuddy de-dup: the commands/ surface is the sole '/' entry point; skills
// stay model-invocable background knowledge. (user-invocable:false hides from
// '/' while keeping model invocation — distinct from disable-model-invocation.)
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\nuser-invocable: false\n---\n\n${adapter}\n\n${body.trimStart()}`;
}
/**

View File

@@ -402,75 +402,75 @@
"scripts/gen-loop-host-contract.cjs": "c7f15237234811a0",
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7",
"skills/gsd-add-tests/SKILL.md": "2cfc1922a2c0f308",
"skills/gsd-ai-integration-phase/SKILL.md": "de0a4cba5ad1651e",
"skills/gsd-audit-fix/SKILL.md": "6f9986ff00316053",
"skills/gsd-audit-milestone/SKILL.md": "44f048f405be71d6",
"skills/gsd-audit-uat/SKILL.md": "df085a31c102fb9e",
"skills/gsd-autonomous/SKILL.md": "446aadb5cfbf52c6",
"skills/gsd-capture/SKILL.md": "9f6b83a85441693c",
"skills/gsd-cleanup/SKILL.md": "03ba6edc18b933d3",
"skills/gsd-code-review/SKILL.md": "6add7a9afc1f6a97",
"skills/gsd-complete-milestone/SKILL.md": "e10a29da26ba0bd4",
"skills/gsd-config/SKILL.md": "0284ff80c1ed0898",
"skills/gsd-debug/SKILL.md": "4b43729be1dc2eaf",
"skills/gsd-discuss-phase/SKILL.md": "c985a6f4c161fb4d",
"skills/gsd-docs-update/SKILL.md": "670c98be5994cb11",
"skills/gsd-eval-review/SKILL.md": "bb375b7ebbc416af",
"skills/gsd-execute-phase/SKILL.md": "695061c3dfea5b2c",
"skills/gsd-explore/SKILL.md": "34681d40134229b5",
"skills/gsd-extract-learnings/SKILL.md": "3073d3a5fe7ebd07",
"skills/gsd-fast/SKILL.md": "83eba8ec0f933cbe",
"skills/gsd-forensics/SKILL.md": "7375ebd3a971da09",
"skills/gsd-graphify/SKILL.md": "721039d5f186982d",
"skills/gsd-health/SKILL.md": "614dc5d4f9a6bc63",
"skills/gsd-help/SKILL.md": "e346f3202ffae1a1",
"skills/gsd-import/SKILL.md": "308a5ecc47871055",
"skills/gsd-inbox/SKILL.md": "f9b4ff17883ee7e4",
"skills/gsd-ingest-docs/SKILL.md": "01ed9183a00252ee",
"skills/gsd-manager/SKILL.md": "62cd9f671a39e8a4",
"skills/gsd-map-codebase/SKILL.md": "383559aca64201e8",
"skills/gsd-mempalace-capture/SKILL.md": "f14977d3f78291c6",
"skills/gsd-mempalace-recall/SKILL.md": "56f3037091a8bd9a",
"skills/gsd-milestone-summary/SKILL.md": "c5dbbc8edb97a7f7",
"skills/gsd-mvp-phase/SKILL.md": "9d8e2999fa9f7830",
"skills/gsd-new-milestone/SKILL.md": "642d0741c89b911a",
"skills/gsd-new-project/SKILL.md": "18f587ac0b390d94",
"skills/gsd-next/SKILL.md": "75ef1905cb7425e1",
"skills/gsd-ns-context/SKILL.md": "dcf5711653d57354",
"skills/gsd-ns-ideate/SKILL.md": "0114f0bc5501c1e3",
"skills/gsd-ns-manage/SKILL.md": "f3232d05d5263f5b",
"skills/gsd-ns-project/SKILL.md": "81d802dd3ee2e1ff",
"skills/gsd-ns-review/SKILL.md": "8c686e92f293bfdd",
"skills/gsd-ns-workflow/SKILL.md": "c379192bd17d47d7",
"skills/gsd-onboard/SKILL.md": "cae1f9382469fa00",
"skills/gsd-pause-work/SKILL.md": "10e7531a2cb392ad",
"skills/gsd-phase/SKILL.md": "1a2c9b64c1af7ffc",
"skills/gsd-plan-phase/SKILL.md": "b7846866b27c6d3f",
"skills/gsd-plan-review-convergence/SKILL.md": "8e55611cdbd4715c",
"skills/gsd-pr-branch/SKILL.md": "725727e3c2b66543",
"skills/gsd-profile-user/SKILL.md": "eb6842b23f1f9256",
"skills/gsd-progress/SKILL.md": "117243aef3e64b85",
"skills/gsd-quick/SKILL.md": "0fdc1838759e2d25",
"skills/gsd-resume-work/SKILL.md": "492e402cb66b1fcf",
"skills/gsd-review-backlog/SKILL.md": "cf7ff999c96d2f76",
"skills/gsd-review/SKILL.md": "f5ebef9d2d12b2ad",
"skills/gsd-secure-phase/SKILL.md": "970f53251c7adff7",
"skills/gsd-settings/SKILL.md": "c06a8c93de64314e",
"skills/gsd-ship/SKILL.md": "c7eeb21a15c66189",
"skills/gsd-sketch/SKILL.md": "44605f8ec5808162",
"skills/gsd-spec-phase/SKILL.md": "5c48117fbb7aa595",
"skills/gsd-spike/SKILL.md": "3850674027d81c2a",
"skills/gsd-stats/SKILL.md": "ebbd0d39b5cee9a9",
"skills/gsd-surface/SKILL.md": "9b869434321867c3",
"skills/gsd-thread/SKILL.md": "621cde6272262eef",
"skills/gsd-ui-phase/SKILL.md": "505ad61ef61c9e9c",
"skills/gsd-ui-review/SKILL.md": "dc79fe0ad42f4948",
"skills/gsd-ultraplan-phase/SKILL.md": "51c2fe437b7d9e22",
"skills/gsd-undo/SKILL.md": "886857ca7dbf15a5",
"skills/gsd-update/SKILL.md": "d87ddffc97e68ec5",
"skills/gsd-validate-phase/SKILL.md": "9148179afa585dad",
"skills/gsd-verify-work/SKILL.md": "28367b353ddfb65d",
"skills/gsd-workspace/SKILL.md": "9b1cf8be726b7b3a",
"skills/gsd-workstreams/SKILL.md": "55a94c63e32f85a6"
"skills/gsd-add-tests/SKILL.md": "1719810d0f8dd79e",
"skills/gsd-ai-integration-phase/SKILL.md": "9c8242023a8bcc29",
"skills/gsd-audit-fix/SKILL.md": "2d2ee3894efbee1c",
"skills/gsd-audit-milestone/SKILL.md": "d953bd7170402fc3",
"skills/gsd-audit-uat/SKILL.md": "97794348d617f873",
"skills/gsd-autonomous/SKILL.md": "93eaad5fa3cb4d5a",
"skills/gsd-capture/SKILL.md": "0a3e8e1ec622ac20",
"skills/gsd-cleanup/SKILL.md": "1c15f1f846614fdf",
"skills/gsd-code-review/SKILL.md": "20510d2cdc0a087b",
"skills/gsd-complete-milestone/SKILL.md": "409785384c03e9a1",
"skills/gsd-config/SKILL.md": "bfb2c9a68658ea8a",
"skills/gsd-debug/SKILL.md": "9b9bd960dec805a6",
"skills/gsd-discuss-phase/SKILL.md": "528dde84ab54176b",
"skills/gsd-docs-update/SKILL.md": "be12b6b48447df38",
"skills/gsd-eval-review/SKILL.md": "892d1f5fb5665941",
"skills/gsd-execute-phase/SKILL.md": "e866ad459c9b0f5b",
"skills/gsd-explore/SKILL.md": "68c3b41f8203e7d4",
"skills/gsd-extract-learnings/SKILL.md": "9356725f248182f1",
"skills/gsd-fast/SKILL.md": "7348338dca150e4b",
"skills/gsd-forensics/SKILL.md": "dca13ed03d434edc",
"skills/gsd-graphify/SKILL.md": "1dd14bfcdb6d0f1b",
"skills/gsd-health/SKILL.md": "6b18ac9d649f63a8",
"skills/gsd-help/SKILL.md": "d159ee08c7fc95a4",
"skills/gsd-import/SKILL.md": "01ebb01767d47e8b",
"skills/gsd-inbox/SKILL.md": "8f7e1c95413968d5",
"skills/gsd-ingest-docs/SKILL.md": "1a2e87caa9011682",
"skills/gsd-manager/SKILL.md": "8a506b1b8b3841c3",
"skills/gsd-map-codebase/SKILL.md": "74a827b893d70dfa",
"skills/gsd-mempalace-capture/SKILL.md": "b5ad8d540b6320be",
"skills/gsd-mempalace-recall/SKILL.md": "04364d3012644204",
"skills/gsd-milestone-summary/SKILL.md": "1d0b245b74176236",
"skills/gsd-mvp-phase/SKILL.md": "485356f67c196015",
"skills/gsd-new-milestone/SKILL.md": "882af2f4995929b7",
"skills/gsd-new-project/SKILL.md": "daad247764b4e466",
"skills/gsd-next/SKILL.md": "ef4221025da5ed3a",
"skills/gsd-ns-context/SKILL.md": "adf3509008907983",
"skills/gsd-ns-ideate/SKILL.md": "701590ef59fefa9c",
"skills/gsd-ns-manage/SKILL.md": "2820ca89ec707808",
"skills/gsd-ns-project/SKILL.md": "799f9651be83ec24",
"skills/gsd-ns-review/SKILL.md": "1dac39c855555b0b",
"skills/gsd-ns-workflow/SKILL.md": "975c89ee2846b40c",
"skills/gsd-onboard/SKILL.md": "c7f216749ba4c09c",
"skills/gsd-pause-work/SKILL.md": "7d310e7329b7ae7d",
"skills/gsd-phase/SKILL.md": "49fca93e5f2ccc70",
"skills/gsd-plan-phase/SKILL.md": "62dc688c24e59f18",
"skills/gsd-plan-review-convergence/SKILL.md": "c2793cacf3f40d70",
"skills/gsd-pr-branch/SKILL.md": "a8f8838a6aa5e1dd",
"skills/gsd-profile-user/SKILL.md": "3be8d50399836ef2",
"skills/gsd-progress/SKILL.md": "1245ea9e6af6ae25",
"skills/gsd-quick/SKILL.md": "05670e9238811a5f",
"skills/gsd-resume-work/SKILL.md": "6c8b592295a2edc9",
"skills/gsd-review-backlog/SKILL.md": "b1070503042ddb13",
"skills/gsd-review/SKILL.md": "27bb3cfc1f54f650",
"skills/gsd-secure-phase/SKILL.md": "02828a6254eae50e",
"skills/gsd-settings/SKILL.md": "8f32c4a4085bc72a",
"skills/gsd-ship/SKILL.md": "c54eb2511f8e8417",
"skills/gsd-sketch/SKILL.md": "a1ce42548d7287bd",
"skills/gsd-spec-phase/SKILL.md": "7cfdb9fc879d9b6b",
"skills/gsd-spike/SKILL.md": "c9b2e4ab15ade118",
"skills/gsd-stats/SKILL.md": "5796e221ab9ab7ae",
"skills/gsd-surface/SKILL.md": "d219dd20380caa2f",
"skills/gsd-thread/SKILL.md": "a73159bc8269dd68",
"skills/gsd-ui-phase/SKILL.md": "c69cbdf977e2a840",
"skills/gsd-ui-review/SKILL.md": "fbed8745f1abb289",
"skills/gsd-ultraplan-phase/SKILL.md": "749373bb067636fa",
"skills/gsd-undo/SKILL.md": "6ebcaaaea575bf85",
"skills/gsd-update/SKILL.md": "df643c197bd9ad5e",
"skills/gsd-validate-phase/SKILL.md": "10a5d3f8de0d9683",
"skills/gsd-verify-work/SKILL.md": "eec11aab679a8116",
"skills/gsd-workspace/SKILL.md": "c2e42b0b5c105991",
"skills/gsd-workstreams/SKILL.md": "14f44c061fef78f8"
}

View File

@@ -2046,6 +2046,7 @@ const { createTempDir, cleanup } = require('./helpers.cjs');
const {
convertClaudeCommandToCodebuddyCommand,
convertClaudeCommandToCodebuddySkill,
convertClaudeCommandToCursorSkill,
} = require('../bin/install.js');
const {
@@ -2234,6 +2235,57 @@ describe('enh-789 — installRuntimeArtifacts codebuddy emits commands and skill
});
});
// ─── #2341: extend the #789 de-dup to Cursor ─────────────────────────────────
// Cursor installs BOTH a skills surface and a commands surface (#785/#803), and
// surfaces both in its '/' menu — so every /gsd-* appeared twice. The #789 fix
// (skills user-invocable:false → model-invocable but out of '/') was scoped to
// CodeBuddy only and never applied to Cursor. Cursor honors the same SKILL.md
// `user-invocable` convention (verified: user-invocable:false hides a skill from
// '/' while keeping it model-invocable, distinct from disable-model-invocation).
// Fix: emit Cursor skills with user-invocable:false so commands are the single
// '/' entry point.
describe('fix-2341 — Cursor skills marked user-invocable:false', () => {
test('convertClaudeCommandToCursorSkill emits user-invocable: false', () => {
const src = [
'---',
'name: gsd:help',
'description: Show help',
'---',
'',
'# body',
'',
].join('\n');
const out = convertClaudeCommandToCursorSkill(src, 'gsd-help');
assert.ok(/^user-invocable:\s*false\s*$/m.test(out),
`Cursor SKILL.md frontmatter must hide skill from '/' menu (user-invocable: false). Got:\n${out}`);
});
test('installed cursor skills/gsd-help/SKILL.md is hidden from the / menu', (t) => {
const configDir = createTempDir('gsd-fix2341-skillhide-');
t.after(() => cleanup(configDir));
installRuntimeArtifacts('cursor', configDir, 'global', RESOLVED_CORE);
const skill = fs.readFileSync(path.join(configDir, 'skills', 'gsd-help', 'SKILL.md'), 'utf8');
assert.ok(/^user-invocable:\s*false\s*$/m.test(skill),
'installed Cursor SKILL.md must set user-invocable: false so it is not a duplicate / entry');
});
test('cursor still installs the commands surface (the single / entry point)', (t) => {
const configDir = createTempDir('gsd-fix2341-cmd-');
t.after(() => cleanup(configDir));
installRuntimeArtifacts('cursor', configDir, 'global', RESOLVED_CORE);
// The commands surface stays user-invocable — de-dup hides the skill, not the command.
assert.ok(fs.existsSync(path.join(configDir, 'commands', 'gsd-help.md')),
'commands/gsd-help.md (the / entry point) must still be installed');
const cmd = fs.readFileSync(path.join(configDir, 'commands', 'gsd-help.md'), 'utf8');
assert.ok(!/^user-invocable:\s*false\s*$/m.test(cmd),
'the command surface must remain user-invocable (only the skill is hidden)');
});
});
// ─── Uninstall contract ──────────────────────────────────────────────────────
describe('enh-789 — uninstallRuntimeArtifacts removes codebuddy commands', () => {