fix(#2644): deduplicate Cursor slash menu (#2812)

* fix(#2644): deduplicate Cursor slash menu

* fix: preserve installer executable mode

* chore: add changeset for PR #2812

* test(#2644): acknowledge Cursor emission changes

* test(#2644): drop spent emitted drift acknowledgments

* fix(#2644): remove retired Cursor command converter

---------

Co-authored-by: clezcoding <clezcoding@users.noreply.github.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
𝚌𝚕𝚎𝚣𝚌𝚘𝚍𝚒𝚗𝚐
2026-08-03 18:05:45 +02:00
committed by GitHub
parent 067a4d1c6c
commit 88f6d9bd1b
29 changed files with 727 additions and 360 deletions

View File

@@ -0,0 +1,71 @@
/**
* Installer migration: retire Cursor's duplicate commands/ surface (#2644).
*
* Cursor discovers GSD skills as slash-menu entries while also keeping them
* model-invocable. Older GSD releases installed the same workflows again as
* commands/gsd-*.md, so every action appeared twice. The skills remain the
* sole workflow surface; this migration removes only old command files proven
* managed by gsd-file-manifest.json. Modified files are backed up first and
* unmanifested files are preserved.
*/
import fs from 'node:fs';
import path from 'node:path';
interface ClassifiedArtifact {
classification: string;
}
interface MigrationAction {
type: 'remove-managed' | 'backup-and-remove';
relPath: string;
reason: string;
ownershipEvidence: string;
}
interface MigrationPlanContext {
configDir: string;
classifyArtifact(relPath: string): ClassifiedArtifact;
}
const COMMANDS_DIR = 'commands';
const REASON =
'Cursor exposes skills directly in the slash menu; the parallel command file duplicated the same GSD action (#2644)';
const OWNERSHIP_EVIDENCE =
'pre-#2644 Cursor installs record commands/gsd-*.md in gsd-file-manifest.json';
const migration = {
id: '2026-07-29-cursor-retire-commands-surface',
title: 'Retire Cursor duplicate commands surface',
description:
'Remove manifest-managed Cursor commands/gsd-*.md files now that skills are the single slash-menu and model-invocation surface.',
introducedIn: '1.8.1',
runtimes: ['cursor'],
scopes: ['global', 'local'],
destructive: true,
plan: (ctx: MigrationPlanContext): MigrationAction[] => {
const commandsDir = path.join(ctx.configDir, COMMANDS_DIR);
let entries: fs.Dirent[];
try {
if (!fs.existsSync(commandsDir) || fs.lstatSync(commandsDir).isSymbolicLink()) return [];
entries = fs.readdirSync(commandsDir, { withFileTypes: true });
} catch {
return [];
}
const actions: MigrationAction[] = [];
for (const entry of entries) {
if (!entry.isFile() || !entry.name.startsWith('gsd-') || !entry.name.endsWith('.md')) continue;
const relPath = path.posix.join(COMMANDS_DIR, entry.name);
const artifact = ctx.classifyArtifact(relPath);
if (artifact.classification === 'managed-pristine') {
actions.push({ type: 'remove-managed', relPath, reason: REASON, ownershipEvidence: OWNERSHIP_EVIDENCE });
} else if (artifact.classification === 'managed-modified') {
actions.push({ type: 'backup-and-remove', relPath, reason: REASON, ownershipEvidence: OWNERSHIP_EVIDENCE });
}
}
return actions;
},
};
export = migration;