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

@@ -8,6 +8,10 @@
//
// Fix (install-profiles.cts): gate nesting on full OR full-equivalent (all routerStems
// present in the concrete Set) so that the surface path preserves nesting.
//
// NOTE: As of #924 Claude has been REVERTED to FLAT. This test now uses Cline as the
// representative nested runtime. The original claude-global test below is updated to
// assert the flat layout (>= 60 top-level gsd-* entries, concrete skills discoverable).
'use strict';
@@ -29,18 +33,19 @@ const { resolveRuntimeArtifactLayout } = require('../gsd-core/bin/lib/runtime-ar
const { cleanup } = require('./helpers.cjs');
describe('issue-69: applySurface preserves nested skill layout (no re-flatten)', () => {
test('claude global full: applySurface keeps 6 router dirs and nested gsd-ns-workflow/skills/plan-phase/SKILL.md', (t) => {
// #924: Claude is now flat; use Cline as the representative nested runtime.
test('cline global full: applySurface keeps 6 router dirs and nested gsd-ns-manage/skills/help/SKILL.md', (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-69-surface-'));
t.after(() => { try { cleanup(tmpDir); } catch { /* best-effort */ } });
// Step 1: full install
const manifest = loadSkillsManifest(COMMANDS_GSD);
const resolved = resolveProfile({ modes: ['full'], manifest });
installRuntimeArtifacts('claude', tmpDir, 'global', resolved);
installRuntimeArtifacts('cline', tmpDir, 'global', resolved);
const skillsDir = path.join(tmpDir, 'skills');
// Sanity: install must produce nested layout
// Sanity: install must produce nested layout (6 top-level router dirs)
const topLevelAfterInstall = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
assert.strictEqual(
topLevelAfterInstall.length,
@@ -53,7 +58,7 @@ describe('issue-69: applySurface preserves nested skill layout (no re-flatten)',
);
// Step 2: applySurface (full surface, no surface state file → resolves to full)
const layout = resolveRuntimeArtifactLayout('claude', tmpDir, 'global');
const layout = resolveRuntimeArtifactLayout('cline', tmpDir, 'global');
applySurface(tmpDir, layout, manifest);
// Step 3: assert nested layout is preserved after applySurface
@@ -77,4 +82,50 @@ describe('issue-69: applySurface preserves nested skill layout (no re-flatten)',
'After applySurface: gsd-plan-phase/ must NOT exist at top level (#69 re-flatten regression guard)',
);
});
// #924 companion: Claude must use FLAT layout and applySurface must NOT re-nest it.
test('claude global full: install produces flat layout and applySurface preserves it (#924)', (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-924-69-'));
t.after(() => { try { cleanup(tmpDir); } catch { /* best-effort */ } });
const manifest = loadSkillsManifest(COMMANDS_GSD);
const resolved = resolveProfile({ modes: ['full'], manifest });
installRuntimeArtifacts('claude', tmpDir, 'global', resolved);
const skillsDir = path.join(tmpDir, 'skills');
// Install must produce FLAT layout (>= 60 gsd-* dirs)
const topLevelAfterInstall = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
assert.ok(
topLevelAfterInstall.length >= 60,
`Claude install must produce >= 60 gsd-* top-level dirs (flat, #924). Got ${topLevelAfterInstall.length}.`,
);
// gsd-plan-phase must be directly at top level
assert.ok(
fs.existsSync(path.join(skillsDir, 'gsd-plan-phase', 'SKILL.md')),
'After claude install: gsd-plan-phase/SKILL.md must be at top level (flat layout, #924)',
);
// No nested skills/ subdirs under gsd-ns-* in Claude
assert.ok(
!fs.existsSync(path.join(skillsDir, 'gsd-ns-workflow', 'skills')),
'After claude install: gsd-ns-workflow/skills/ must NOT exist (flat layout, no nesting, #924)',
);
// applySurface must preserve flat layout
const layout = resolveRuntimeArtifactLayout('claude', tmpDir, 'global');
applySurface(tmpDir, layout, manifest);
const topLevelAfterSurface = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
assert.ok(
topLevelAfterSurface.length >= 60,
`After applySurface: claude must still have >= 60 gsd-* dirs (flat preserved). Got ${topLevelAfterSurface.length}.`,
);
assert.ok(
fs.existsSync(path.join(skillsDir, 'gsd-plan-phase', 'SKILL.md')),
'After applySurface: gsd-plan-phase/SKILL.md must remain at top level (#924)',
);
});
});