Emit the 6 gsd-ns-* routers as the only top-level skill bundles and nest the ~61 concrete skills under <router>/skills/<name>/SKILL.md on runtimes with confirmed non-recursive skill loaders (claude global, cline, qwen, hermes, augment, trae, antigravity). Router bodies rewrite their routing tables from Skill-tool dispatch to a Read skills/<name>/SKILL.md pattern. Recursive/unconfirmed loaders (cursor, codex, copilot, windsurf, codebuddy, opencode, kilo) keep the flat layout. Completes the v1.40 namespace architecture (#2792) so the eager skill listing drops to ~6 entries. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
3.8 KiB
JavaScript
81 lines
3.8 KiB
JavaScript
// #69 regression: applySurface must NOT re-flatten the nested skill layout
|
|
//
|
|
// Bug: stageSkillsForRuntimeAsSkills gated nesting on `resolvedProfile.skills === '*'`
|
|
// (the sentinel). applySurface → resolveSurface materializes the full profile into a
|
|
// concrete Set<string>, so the sentinel check was never true on the surface path.
|
|
// Result: applySurface called kind.stage(resolved) → stageSkillsForRuntimeAsSkills with
|
|
// a concrete Set → doNest = false → flat layout, overwriting the nested install.
|
|
//
|
|
// 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.
|
|
|
|
'use strict';
|
|
|
|
process.env.GSD_TEST_MODE = '1';
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const os = require('node:os');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const COMMANDS_GSD = path.join(ROOT, 'commands', 'gsd');
|
|
|
|
const { installRuntimeArtifacts } = require('../bin/install.js');
|
|
const { applySurface } = require('../gsd-core/bin/lib/surface.cjs');
|
|
const { loadSkillsManifest, resolveProfile } = require('../gsd-core/bin/lib/install-profiles.cjs');
|
|
const { resolveRuntimeArtifactLayout } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
|
|
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) => {
|
|
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);
|
|
|
|
const skillsDir = path.join(tmpDir, 'skills');
|
|
|
|
// Sanity: install must produce nested layout
|
|
const topLevelAfterInstall = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
|
|
assert.strictEqual(
|
|
topLevelAfterInstall.length,
|
|
6,
|
|
`Install must produce exactly 6 gsd-* top-level dirs (routers). Got ${topLevelAfterInstall.length}: [${topLevelAfterInstall.join(', ')}]`,
|
|
);
|
|
assert.ok(
|
|
fs.existsSync(path.join(skillsDir, 'gsd-ns-workflow', 'skills', 'plan-phase', 'SKILL.md')),
|
|
'After install: gsd-ns-workflow/skills/plan-phase/SKILL.md must exist',
|
|
);
|
|
|
|
// Step 2: applySurface (full surface, no surface state file → resolves to full)
|
|
const layout = resolveRuntimeArtifactLayout('claude', tmpDir, 'global');
|
|
applySurface(tmpDir, layout, manifest);
|
|
|
|
// Step 3: assert nested layout is preserved after applySurface
|
|
const topLevelAfterSurface = fs.readdirSync(skillsDir).filter((n) => n.startsWith('gsd-'));
|
|
assert.strictEqual(
|
|
topLevelAfterSurface.length,
|
|
6,
|
|
`After applySurface: expected exactly 6 gsd-* top-level dirs (routers only). Got ${topLevelAfterSurface.length}: [${topLevelAfterSurface.join(', ')}]. ` +
|
|
'Re-flattening detected: applySurface must preserve nested layout (#69 regression).',
|
|
);
|
|
|
|
// The nested SKILL.md must still exist (not re-flattened to top-level concrete dir)
|
|
assert.ok(
|
|
fs.existsSync(path.join(skillsDir, 'gsd-ns-workflow', 'skills', 'plan-phase', 'SKILL.md')),
|
|
'After applySurface: gsd-ns-workflow/skills/plan-phase/SKILL.md must still exist (nested layout preserved)',
|
|
);
|
|
|
|
// The concrete skill must NOT have been promoted to a top-level flat dir
|
|
assert.ok(
|
|
!fs.existsSync(path.join(skillsDir, 'gsd-plan-phase', 'SKILL.md')),
|
|
'After applySurface: gsd-plan-phase/ must NOT exist at top level (#69 re-flatten regression guard)',
|
|
);
|
|
});
|
|
});
|