fix(#924): revert Claude to flat skill layout so concrete skills are discoverable (#928)

PR #883 nested Claude skills 3 levels deep under gsd-ns-*/skills/<stem>/SKILL.md.
Claude Code's Skill tool scans only one level under ~/.claude/skills/ — nested
concrete skills were never listed and Skill(skill="gsd-plan-phase") calls failed.

Revert to flat layout: all ~61 concrete skills at ~/.claude/skills/gsd-<name>/SKILL.md.
The 6 other runtimes confirmed as non-recursive scanners (cline, qwen, hermes, augment,
trae, antigravity) retain their nested layout — only Claude changes.

Tradeoff: ~61 top-level skill dirs return to the flat install, but they are discoverable
and invokable. Nested concretes were invisible to the Skill tool entirely.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-09 08:59:20 -04:00
committed by GitHub
parent b866b95296
commit 03d9fcdd65
8 changed files with 300 additions and 48 deletions

View File

@@ -32,7 +32,8 @@ const { COMMANDS_GSD, ROUTER_STEMS, routerChildren } = require('./helpers/nested
// ---------------------------------------------------------------------------
const NEST = [
{ runtime: 'claude', scope: 'global', skillsSub: 'skills', prefix: 'gsd-' },
// Claude reverted to flat (#924: nested layout breaks Skill-tool discovery on Claude Code).
// Only the 6 runtimes below keep the nested layout.
{ runtime: 'cline', scope: 'global', skillsSub: 'skills', prefix: 'gsd-' },
{ runtime: 'qwen', scope: 'global', skillsSub: 'skills', prefix: 'gsd-' },
{ runtime: 'hermes', scope: 'global', skillsSub: 'skills/gsd', prefix: '' },
@@ -42,6 +43,9 @@ const NEST = [
];
const FLAT = [
// Claude reverted to flat (#924): Claude Code scans only one level under ~/.claude/skills/
// so nested concretes were never discoverable by the Skill tool.
{ runtime: 'claude', scope: 'global', skillsSub: 'skills' },
{ runtime: 'cursor', scope: 'global', skillsSub: 'skills' },
{ runtime: 'codex', scope: 'global', skillsSub: 'skills' },
{ runtime: 'copilot', scope: 'global', skillsSub: 'skills' },
@@ -200,10 +204,13 @@ for (const { runtime, scope, skillsSub, prefix } of NEST) {
}
// ---------------------------------------------------------------------------
// claude extra: total top-level gsd- count must equal exactly 6
// claude extra: total top-level gsd- count must be >= 60 (FLAT, #924)
//
// Pre-#924 (nested) this block asserted exactly 6 (only routers).
// Post-#924 (flat) Claude has all concrete skills at the top level.
// ---------------------------------------------------------------------------
describe('claude: total top-level gsd- entries == 6', () => {
describe('claude: total top-level gsd- entries >= 60 (flat layout, #924)', () => {
let tmpDir;
before(() => {
@@ -216,15 +223,15 @@ describe('claude: total top-level gsd- entries == 6', () => {
}
});
test('claude: total top-level gsd- skill entries == 6', () => {
test('claude: >= 60 gsd-* top-level skill entries (concrete flat layout, not nested)', () => {
const skillsDir = path.join(tmpDir, 'skills');
assert.ok(fs.existsSync(skillsDir), 'skills/ dir must exist');
const topLevel = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
assert.strictEqual(
topLevel.length,
6,
`Expected exactly 6 gsd-* top-level entries under claude/skills, got ${topLevel.length}: [${topLevel.join(', ')}]`,
assert.ok(
topLevel.length >= 60,
`Expected >= 60 gsd-* top-level entries under claude/skills (flat layout after #924 fix). ` +
`Got ${topLevel.length}: [${topLevel.slice(0, 10).join(', ')}${topLevel.length > 10 ? ', …' : ''}]`,
);
});
});