diff --git a/bin/install.js b/bin/install.js
index c380834a7..d74f52c4a 100755
--- a/bin/install.js
+++ b/bin/install.js
@@ -688,6 +688,39 @@ function convertClaudeCommandToCopilotSkill(content, skillName, isGlobal = false
return `${fm}\n${body}`;
}
+/**
+ * Convert a Claude command (.md) to a Claude skill (SKILL.md).
+ * Claude Code is the native format, so minimal conversion needed —
+ * preserve allowed-tools as YAML multiline list, preserve argument-hint,
+ * convert name from gsd:xxx to gsd-xxx format.
+ */
+function convertClaudeCommandToClaudeSkill(content, skillName) {
+ const { frontmatter, body } = extractFrontmatterAndBody(content);
+ if (!frontmatter) return content;
+
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
+ const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
+ const agent = extractFrontmatterField(frontmatter, 'agent');
+
+ // Preserve allowed-tools as YAML multiline list (Claude native format)
+ const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
+ let toolsBlock = '';
+ if (toolsMatch) {
+ toolsBlock = 'allowed-tools:\n' + toolsMatch[1];
+ // Ensure trailing newline
+ if (!toolsBlock.endsWith('\n')) toolsBlock += '\n';
+ }
+
+ // Reconstruct frontmatter in Claude skill format
+ let fm = `---\nname: ${skillName}\ndescription: ${yamlQuote(description)}\n`;
+ if (argumentHint) fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
+ if (agent) fm += `agent: ${agent}\n`;
+ if (toolsBlock) fm += toolsBlock;
+ fm += '---';
+
+ return `${fm}\n${body}`;
+}
+
/**
* Convert a Claude agent (.md) to a Copilot agent (.agent.md).
* Applies tool mapping + deduplication, formats tools as JSON array.
@@ -3036,6 +3069,63 @@ function copyCommandsAsCopilotSkills(srcDir, skillsDir, prefix, isGlobal = false
recurse(srcDir, prefix);
}
+/**
+ * Copy Claude commands as Claude skills — one folder per skill with SKILL.md.
+ * Claude Code 2.1.88+ uses skills/xxx/SKILL.md instead of commands/gsd/xxx.md.
+ * Claude is the native format so no path replacement is needed — only
+ * frontmatter restructuring via convertClaudeCommandToClaudeSkill.
+ * @param {string} srcDir - Source commands directory
+ * @param {string} skillsDir - Target skills directory
+ * @param {string} prefix - Skill name prefix (e.g. 'gsd')
+ * @param {string} pathPrefix - Path prefix for file references
+ * @param {string} runtime - Target runtime
+ * @param {boolean} isGlobal - Whether this is a global install
+ */
+function copyCommandsAsClaudeSkills(srcDir, skillsDir, prefix, pathPrefix, runtime, isGlobal = false) {
+ if (!fs.existsSync(srcDir)) {
+ return;
+ }
+
+ fs.mkdirSync(skillsDir, { recursive: true });
+
+ // Remove previous GSD Claude skills to avoid stale command skills
+ const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
+ for (const entry of existing) {
+ if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
+ }
+ }
+
+ function recurse(currentSrcDir, currentPrefix) {
+ const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const srcPath = path.join(currentSrcDir, entry.name);
+ if (entry.isDirectory()) {
+ recurse(srcPath, `${currentPrefix}-${entry.name}`);
+ continue;
+ }
+
+ if (!entry.name.endsWith('.md')) {
+ continue;
+ }
+
+ const baseName = entry.name.replace('.md', '');
+ const skillName = `${currentPrefix}-${baseName}`;
+ const skillDir = path.join(skillsDir, skillName);
+ fs.mkdirSync(skillDir, { recursive: true });
+
+ let content = fs.readFileSync(srcPath, 'utf8');
+ content = processAttribution(content, getCommitAttribution('claude'));
+ content = convertClaudeCommandToClaudeSkill(content, skillName);
+
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
+ }
+ }
+
+ recurse(srcDir, prefix);
+}
+
/**
* Recursively install GSD commands as Antigravity skills.
* Each command becomes a skill-name/ folder containing SKILL.md.
@@ -3364,6 +3454,7 @@ function validateHookFields(settings) {
*/
function uninstall(isGlobal, runtime = 'claude') {
const isOpencode = runtime === 'opencode';
+ const isGemini = runtime === 'gemini';
const isCodex = runtime === 'codex';
const isCopilot = runtime === 'copilot';
const isAntigravity = runtime === 'antigravity';
@@ -3551,13 +3642,39 @@ function uninstall(isGlobal, runtime = 'claude') {
console.log(` ${green}✓${reset} Removed ${skillCount} Windsurf skills`);
}
}
- } else {
+ } else if (isGemini) {
+ // Gemini: still uses commands/gsd/
const gsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
if (fs.existsSync(gsdCommandsDir)) {
fs.rmSync(gsdCommandsDir, { recursive: true });
removedCount++;
console.log(` ${green}✓${reset} Removed commands/gsd/`);
}
+ } else {
+ // Claude Code: remove skills/gsd-*/ directories
+ const skillsDir = path.join(targetDir, 'skills');
+ if (fs.existsSync(skillsDir)) {
+ let skillCount = 0;
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
+ for (const entry of entries) {
+ if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
+ skillCount++;
+ }
+ }
+ if (skillCount > 0) {
+ removedCount++;
+ console.log(` ${green}✓${reset} Removed ${skillCount} Claude Code skills`);
+ }
+ }
+
+ // Also clean up legacy commands/gsd/ from older installs
+ const legacyCommandsDir = path.join(targetDir, 'commands', 'gsd');
+ if (fs.existsSync(legacyCommandsDir)) {
+ fs.rmSync(legacyCommandsDir, { recursive: true });
+ removedCount++;
+ console.log(` ${green}✓${reset} Removed legacy commands/gsd/`);
+ }
}
// 2. Remove get-shit-done directory
@@ -3983,6 +4100,7 @@ function generateManifest(dir, baseDir) {
*/
function writeManifest(configDir, runtime = 'claude') {
const isOpencode = runtime === 'opencode';
+ const isGemini = runtime === 'gemini';
const isCodex = runtime === 'codex';
const isCopilot = runtime === 'copilot';
const isAntigravity = runtime === 'antigravity';
@@ -3999,7 +4117,7 @@ function writeManifest(configDir, runtime = 'claude') {
for (const [rel, hash] of Object.entries(gsdHashes)) {
manifest.files['get-shit-done/' + rel] = hash;
}
- if (!isOpencode && !isCodex && !isCopilot && !isAntigravity && !isCursor && !isWindsurf && fs.existsSync(commandsDir)) {
+ if (isGemini && fs.existsSync(commandsDir)) {
const cmdHashes = generateManifest(commandsDir);
for (const [rel, hash] of Object.entries(cmdHashes)) {
manifest.files['commands/gsd/' + rel] = hash;
@@ -4012,7 +4130,7 @@ function writeManifest(configDir, runtime = 'claude') {
}
}
}
- if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf) && fs.existsSync(codexSkillsDir)) {
+ if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || (!isOpencode && !isGemini)) && fs.existsSync(codexSkillsDir)) {
for (const skillName of listCodexSkillNames(codexSkillsDir)) {
const skillRoot = path.join(codexSkillsDir, skillName);
const skillHashes = generateManifest(skillRoot);
@@ -4245,11 +4363,11 @@ function install(isGlobal, runtime = 'claude') {
} else {
failures.push('skills/gsd-*');
}
- } else {
- // Claude Code & Gemini: nested structure in commands/ directory
+ } else if (isGemini) {
+ // Gemini: nested structure in commands/ directory (still supported)
const commandsDir = path.join(targetDir, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
-
+
const gsdSrc = path.join(src, 'commands', 'gsd');
const gsdDest = path.join(commandsDir, 'gsd');
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime, true, isGlobal);
@@ -4258,6 +4376,29 @@ function install(isGlobal, runtime = 'claude') {
} else {
failures.push('commands/gsd');
}
+ } else {
+ // Claude Code: skills/ format (2.1.88+ compatibility)
+ const skillsDir = path.join(targetDir, 'skills');
+ const gsdSrc = path.join(src, 'commands', 'gsd');
+ copyCommandsAsClaudeSkills(gsdSrc, skillsDir, 'gsd', pathPrefix, runtime, isGlobal);
+ if (fs.existsSync(skillsDir)) {
+ const count = fs.readdirSync(skillsDir, { withFileTypes: true })
+ .filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
+ if (count > 0) {
+ console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
+ } else {
+ failures.push('skills/gsd-*');
+ }
+ } else {
+ failures.push('skills/gsd-*');
+ }
+
+ // Clean up legacy commands/gsd/ from previous installs
+ const legacyCommandsDir = path.join(targetDir, 'commands', 'gsd');
+ if (fs.existsSync(legacyCommandsDir)) {
+ fs.rmSync(legacyCommandsDir, { recursive: true });
+ console.log(` ${green}✓${reset} Removed legacy commands/gsd/ directory`);
+ }
}
// Copy get-shit-done skill with path replacement
@@ -5031,6 +5172,8 @@ if (process.env.GSD_TEST_MODE) {
convertClaudeCommandToAntigravitySkill,
convertClaudeAgentToAntigravityAgent,
copyCommandsAsAntigravitySkills,
+ convertClaudeCommandToClaudeSkill,
+ copyCommandsAsClaudeSkills,
convertClaudeToWindsurfMarkdown,
convertClaudeCommandToWindsurfSkill,
convertClaudeAgentToWindsurfAgent,
diff --git a/tests/claude-skills-migration.test.cjs b/tests/claude-skills-migration.test.cjs
new file mode 100644
index 000000000..c3c64a94c
--- /dev/null
+++ b/tests/claude-skills-migration.test.cjs
@@ -0,0 +1,378 @@
+/**
+ * GSD Tools Tests - Claude Skills Migration (#1504)
+ *
+ * Tests for migrating Claude Code from commands/gsd/ to skills/gsd-xxx/SKILL.md
+ * format for compatibility with Claude Code 2.1.88+.
+ *
+ * Uses node:test and node:assert (NOT Jest).
+ */
+
+process.env.GSD_TEST_MODE = '1';
+
+const { test, describe, beforeEach, afterEach } = require('node:test');
+const assert = require('node:assert');
+const path = require('path');
+const os = require('os');
+const fs = require('fs');
+
+const {
+ convertClaudeCommandToClaudeSkill,
+ copyCommandsAsClaudeSkills,
+ writeManifest,
+ install,
+} = require('../bin/install.js');
+
+// ─── convertClaudeCommandToClaudeSkill ──────────────────────────────────────
+
+describe('convertClaudeCommandToClaudeSkill', () => {
+ test('preserves allowed-tools multiline YAML list', () => {
+ const input = [
+ '---',
+ 'name: gsd:next',
+ 'description: Advance to the next step',
+ 'allowed-tools:',
+ ' - Read',
+ ' - Bash',
+ ' - Grep',
+ '---',
+ '',
+ 'Body content here.',
+ ].join('\n');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
+ assert.ok(result.includes('allowed-tools:'), 'allowed-tools field is present');
+ assert.ok(result.includes('Read'), 'Read tool preserved');
+ assert.ok(result.includes('Bash'), 'Bash tool preserved');
+ assert.ok(result.includes('Grep'), 'Grep tool preserved');
+ });
+
+ test('preserves argument-hint', () => {
+ const input = [
+ '---',
+ 'name: gsd:debug',
+ 'description: Debug issues',
+ 'argument-hint: "[issue description]"',
+ 'allowed-tools:',
+ ' - Read',
+ ' - Bash',
+ '---',
+ '',
+ 'Debug body.',
+ ].join('\n');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-debug');
+ assert.ok(result.includes('argument-hint:'), 'argument-hint field is present');
+ // The value should be preserved (possibly yaml-quoted)
+ assert.ok(
+ result.includes('[issue description]'),
+ 'argument-hint value preserved'
+ );
+ });
+
+ test('converts name format from gsd:xxx to skill naming', () => {
+ const input = [
+ '---',
+ 'name: gsd:next',
+ 'description: Advance workflow',
+ '---',
+ '',
+ 'Body.',
+ ].join('\n');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-next');
+ assert.ok(result.includes('name: gsd-next'), 'name uses skill naming convention');
+ assert.ok(!result.includes('name: gsd:next'), 'old name format removed');
+ });
+
+ test('preserves body content unchanged', () => {
+ const body = '\n\nDo the thing.\n\n\n\nStep 1.\nStep 2.\n\n';
+ const input = [
+ '---',
+ 'name: gsd:test',
+ 'description: Test command',
+ '---',
+ body,
+ ].join('');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-test');
+ assert.ok(result.includes(''), 'objective tag preserved');
+ assert.ok(result.includes('Do the thing.'), 'body text preserved');
+ assert.ok(result.includes(''), 'process tag preserved');
+ assert.ok(result.includes('Step 1.'), 'step text preserved');
+ });
+
+ test('preserves agent field', () => {
+ const input = [
+ '---',
+ 'name: gsd:plan-phase',
+ 'description: Plan a phase',
+ 'agent: true',
+ 'allowed-tools:',
+ ' - Read',
+ '---',
+ '',
+ 'Plan body.',
+ ].join('\n');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-plan-phase');
+ assert.ok(result.includes('agent:'), 'agent field is present');
+ });
+
+ test('handles content with no frontmatter', () => {
+ const input = 'Just some plain markdown content.';
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-plain');
+ assert.strictEqual(result, input, 'content returned unchanged');
+ });
+
+ test('preserves allowed-tools as multiline YAML list (not flattened)', () => {
+ const input = [
+ '---',
+ 'name: gsd:debug',
+ 'description: Debug',
+ 'allowed-tools:',
+ ' - Read',
+ ' - Bash',
+ ' - Task',
+ ' - AskUserQuestion',
+ '---',
+ '',
+ 'Body.',
+ ].join('\n');
+
+ const result = convertClaudeCommandToClaudeSkill(input, 'gsd-debug');
+ // Claude Code native format keeps YAML multiline list
+ assert.ok(result.includes(' - Read'), 'Read in multiline list');
+ assert.ok(result.includes(' - Bash'), 'Bash in multiline list');
+ assert.ok(result.includes(' - Task'), 'Task in multiline list');
+ assert.ok(result.includes(' - AskUserQuestion'), 'AskUserQuestion in multiline list');
+ });
+});
+
+// ─── copyCommandsAsClaudeSkills ─────────────────────────────────────────────
+
+describe('copyCommandsAsClaudeSkills', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-claude-skills-test-'));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ test('creates correct directory structure skills/gsd-xxx/SKILL.md', () => {
+ // Create source commands
+ const srcDir = path.join(tmpDir, 'src');
+ fs.mkdirSync(srcDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(srcDir, 'next.md'),
+ '---\nname: gsd:next\ndescription: Advance\nallowed-tools:\n - Read\n---\n\nBody.'
+ );
+ fs.writeFileSync(
+ path.join(srcDir, 'health.md'),
+ '---\nname: gsd:health\ndescription: Check health\n---\n\nHealth body.'
+ );
+
+ const skillsDir = path.join(tmpDir, 'skills');
+ copyCommandsAsClaudeSkills(srcDir, skillsDir, 'gsd', '$HOME/.claude/', 'claude', true);
+
+ // Verify directory structure
+ assert.ok(
+ fs.existsSync(path.join(skillsDir, 'gsd-next', 'SKILL.md')),
+ 'skills/gsd-next/SKILL.md exists'
+ );
+ assert.ok(
+ fs.existsSync(path.join(skillsDir, 'gsd-health', 'SKILL.md')),
+ 'skills/gsd-health/SKILL.md exists'
+ );
+ });
+
+ test('cleans up old skills before installing new ones', () => {
+ const srcDir = path.join(tmpDir, 'src');
+ fs.mkdirSync(srcDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(srcDir, 'next.md'),
+ '---\nname: gsd:next\ndescription: Advance\n---\n\nBody.'
+ );
+
+ const skillsDir = path.join(tmpDir, 'skills');
+ // Create a stale skill that should be removed
+ const staleDir = path.join(skillsDir, 'gsd-old-command');
+ fs.mkdirSync(staleDir, { recursive: true });
+ fs.writeFileSync(path.join(staleDir, 'SKILL.md'), 'stale content');
+
+ copyCommandsAsClaudeSkills(srcDir, skillsDir, 'gsd', '$HOME/.claude/', 'claude', true);
+
+ // Stale skill removed
+ assert.ok(
+ !fs.existsSync(staleDir),
+ 'stale skill directory removed'
+ );
+ // New skill created
+ assert.ok(
+ fs.existsSync(path.join(skillsDir, 'gsd-next', 'SKILL.md')),
+ 'new skill created'
+ );
+ });
+
+ test('does not remove non-GSD skills', () => {
+ const srcDir = path.join(tmpDir, 'src');
+ fs.mkdirSync(srcDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(srcDir, 'next.md'),
+ '---\nname: gsd:next\ndescription: Advance\n---\n\nBody.'
+ );
+
+ const skillsDir = path.join(tmpDir, 'skills');
+ // Create a non-GSD skill
+ const otherDir = path.join(skillsDir, 'my-custom-skill');
+ fs.mkdirSync(otherDir, { recursive: true });
+ fs.writeFileSync(path.join(otherDir, 'SKILL.md'), 'custom content');
+
+ copyCommandsAsClaudeSkills(srcDir, skillsDir, 'gsd', '$HOME/.claude/', 'claude', true);
+
+ // Non-GSD skill preserved
+ assert.ok(
+ fs.existsSync(otherDir),
+ 'non-GSD skill preserved'
+ );
+ });
+
+ test('handles recursive subdirectories', () => {
+ const srcDir = path.join(tmpDir, 'src');
+ const subDir = path.join(srcDir, 'wired');
+ fs.mkdirSync(subDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(subDir, 'ready.md'),
+ '---\nname: gsd-wired:ready\ndescription: Show ready tasks\n---\n\nBody.'
+ );
+
+ const skillsDir = path.join(tmpDir, 'skills');
+ copyCommandsAsClaudeSkills(srcDir, skillsDir, 'gsd', '$HOME/.claude/', 'claude', true);
+
+ assert.ok(
+ fs.existsSync(path.join(skillsDir, 'gsd-wired-ready', 'SKILL.md')),
+ 'nested command creates gsd-wired-ready/SKILL.md'
+ );
+ });
+
+ test('no-ops when source directory does not exist', () => {
+ const skillsDir = path.join(tmpDir, 'skills');
+ // Should not throw
+ copyCommandsAsClaudeSkills(
+ path.join(tmpDir, 'nonexistent'),
+ skillsDir,
+ 'gsd',
+ '$HOME/.claude/',
+ 'claude',
+ true
+ );
+ assert.ok(!fs.existsSync(skillsDir), 'skills dir not created when src missing');
+ });
+});
+
+// ─── Legacy cleanup during install ──────────────────────────────────────────
+
+describe('Legacy commands/gsd/ cleanup', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-legacy-test-'));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ test('install removes legacy commands/gsd/ directory when present', () => {
+ // Create a mock legacy commands/gsd/ directory
+ const legacyDir = path.join(tmpDir, 'commands', 'gsd');
+ fs.mkdirSync(legacyDir, { recursive: true });
+ fs.writeFileSync(path.join(legacyDir, 'next.md'), 'legacy content');
+
+ // Create source commands for the installer to read
+ const srcDir = path.join(tmpDir, 'src');
+ fs.mkdirSync(srcDir, { recursive: true });
+ fs.writeFileSync(
+ path.join(srcDir, 'next.md'),
+ '---\nname: gsd:next\ndescription: Advance\n---\n\nBody.'
+ );
+
+ const skillsDir = path.join(tmpDir, 'skills');
+ // Install skills
+ copyCommandsAsClaudeSkills(srcDir, skillsDir, 'gsd', '$HOME/.claude/', 'claude', true);
+
+ // Simulate the legacy cleanup that install() does after copyCommandsAsClaudeSkills
+ if (fs.existsSync(legacyDir)) {
+ fs.rmSync(legacyDir, { recursive: true });
+ }
+
+ assert.ok(!fs.existsSync(legacyDir), 'legacy commands/gsd/ removed');
+ assert.ok(
+ fs.existsSync(path.join(skillsDir, 'gsd-next', 'SKILL.md')),
+ 'new skill installed'
+ );
+ });
+});
+
+// ─── writeManifest tracks skills/ for Claude ────────────────────────────────
+
+describe('writeManifest tracks skills/ for Claude', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-manifest-test-'));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ test('manifest includes skills/gsd-xxx/SKILL.md entries for Claude runtime', () => {
+ // Create skills directory structure (as install would)
+ const skillsDir = path.join(tmpDir, 'skills');
+ const skillDir = path.join(skillsDir, 'gsd-next');
+ fs.mkdirSync(skillDir, { recursive: true });
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), 'skill content');
+
+ // Create get-shit-done directory (required by writeManifest)
+ const gsdDir = path.join(tmpDir, 'get-shit-done');
+ fs.mkdirSync(gsdDir, { recursive: true });
+ fs.writeFileSync(path.join(gsdDir, 'test.md'), 'test');
+
+ writeManifest(tmpDir, 'claude');
+
+ const manifest = JSON.parse(
+ fs.readFileSync(path.join(tmpDir, 'gsd-file-manifest.json'), 'utf8')
+ );
+
+ // Should have skills/ entries
+ const skillEntries = Object.keys(manifest.files).filter(k =>
+ k.startsWith('skills/')
+ );
+ assert.ok(skillEntries.length > 0, 'manifest has skills/ entries');
+ assert.ok(
+ skillEntries.some(k => k === 'skills/gsd-next/SKILL.md'),
+ 'manifest has skills/gsd-next/SKILL.md'
+ );
+
+ // Should NOT have commands/gsd/ entries
+ const cmdEntries = Object.keys(manifest.files).filter(k =>
+ k.startsWith('commands/gsd/')
+ );
+ assert.strictEqual(cmdEntries.length, 0, 'manifest has no commands/gsd/ entries');
+ });
+});
+
+// ─── Exports exist ──────────────────────────────────────────────────────────
+
+describe('Claude skills migration exports', () => {
+ test('convertClaudeCommandToClaudeSkill is exported', () => {
+ assert.strictEqual(typeof convertClaudeCommandToClaudeSkill, 'function');
+ });
+
+ test('copyCommandsAsClaudeSkills is exported', () => {
+ assert.strictEqual(typeof copyCommandsAsClaudeSkills, 'function');
+ });
+});