* test(#947): add regression tests and update stale Hermes assertions - Add bug-947-hermes-gsd-prefix.test.cjs: 12 TDD tests covering fresh install canonical layout, bare-stem migration, manifest key format, and non-Hermes runtime isolation - Update hermes-skills-migration.test.cjs: bare-stem → gsd-prefixed path and name assertions (#947 canonical layout) - Update install-nested-layout.test.cjs: Hermes NEST matrix prefix '' → 'gsd-' - Update install-regressions.test.cjs: Defect #1 now seeds bare-stem dirs (help/, quick/) and asserts gsd-help/ canonical output; use real GSD stems so readGsdCommandNames() migration finds them - Update install-runtime-artifacts.test.cjs: Hermes nested layout and legacy migration assertions align with gsd- prefix - Update install.test.cjs: Hermes install test uses gsd- prefixed paths - Update runtime-artifact-layout.test.cjs: prefix '' → 'gsd-' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#947): restore gsd- prefix on Hermes skills for canonical dispatch Hermes skills were installing under bare-stem paths (skills/gsd/<stem>/SKILL.md, name: <stem>) due to prefix: '' set in ADR-3660 / #3664. This broke /gsd-<stem> dispatch and forced users to invoke skills without the gsd- namespace prefix. - src/runtime-artifact-layout.cts: change Hermes skillsKind prefix from '' to 'gsd-'; skills now land at skills/gsd/gsd-<stem>/SKILL.md with name: gsd-<stem> - bin/install.js _runLegacyInstallMigrations: invert the #3664 migration — remove stale bare-stem dirs (using readGsdCommandNames() to distinguish GSD-owned stems from user content), keep gsd-* dirs which are now canonical - bin/install.js _runLegacyUninstallCleanup: also remove bare-stem dirs on uninstall for clean teardown - bin/install.js uninstallRuntimeArtifacts: post-cleanup removes DESCRIPTION.md and empty skills/gsd/ category dir on Hermes - bin/install.js: remove skillListPrefix Hermes exception (now uses shared 'gsd-' path) - docs/adr/3660-runtime-artifact-layout-module.md: document #947 reversal of the bare-stem sub-decision Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: add changeset for #947 fix (#955) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#947): remove ALL pre-migration bare-stem Hermes skills on reinstall (adversarial review) Replace readGsdCommandNames()-based bare-stem cleanup (which missed skills not in the commands source tree, e.g. dev-preferences) with _removeHermesBareStemDirs(), called AFTER the install loop when the exact set of installed gsd-<stem>/ dirs is authoritative. For every gsd-<stem>/ written this run, the corresponding bare skills/gsd/<stem>/ is removed. User-owned bare dirs with no gsd-<stem> counterpart are preserved. Add two adversarial-review regression tests that FAIL on old code: - bare skills/gsd/dev-preferences/ removed when gsd-dev-preferences/ installed - user-owned bare dir with no gsd-<stem> counterpart is preserved (no over-deletion) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -139,7 +139,7 @@ describe('installRuntimeArtifacts — skills runtimes write gsd-prefixed skill d
|
||||
});
|
||||
|
||||
describe('installRuntimeArtifacts — hermes nested layout', () => {
|
||||
test('hermes: skills/gsd/<stem>/SKILL.md, no gsd- prefix in name', (t) => {
|
||||
test('hermes: skills/gsd/gsd-<stem>/SKILL.md with gsd- prefix in name (#947)', (t) => {
|
||||
const configDir = createTempDir('gsd-ial-hermes-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
@@ -147,9 +147,11 @@ describe('installRuntimeArtifacts — hermes nested layout', () => {
|
||||
|
||||
const nestedDir = path.join(configDir, 'skills', 'gsd');
|
||||
assert.ok(fs.existsSync(nestedDir));
|
||||
assert.ok(fs.existsSync(path.join(nestedDir, 'help', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(path.join(nestedDir, 'gsd-help')),
|
||||
'hermes must NOT have gsd-help prefix');
|
||||
// #947: Hermes now uses canonical gsd- prefix — skills/gsd/gsd-<stem>/SKILL.md
|
||||
assert.ok(fs.existsSync(path.join(nestedDir, 'gsd-help', 'SKILL.md')),
|
||||
'skills/gsd/gsd-help/SKILL.md must exist (canonical gsd- prefix, #947)');
|
||||
assert.ok(!fs.existsSync(path.join(nestedDir, 'help')),
|
||||
'bare-stem skills/gsd/help/ must NOT exist (#947 fix)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -323,17 +325,23 @@ describe('uninstallRuntimeArtifacts — removes gsd-owned entries, preserves for
|
||||
|
||||
if (runtime === 'hermes') {
|
||||
const kind = layout.kinds[0];
|
||||
const destDir = path.join(configDir, kind.destSubpath);
|
||||
const destDir = path.join(configDir, kind.destSubpath); // skills/gsd
|
||||
// Seed a gsd-* prefixed skill (canonical #947 layout) and a bare-stem skill (#3664 era)
|
||||
fs.mkdirSync(path.join(destDir, 'gsd-help'), { recursive: true });
|
||||
fs.writeFileSync(path.join(destDir, 'gsd-help', 'SKILL.md'), '# gsd-help\n');
|
||||
fs.mkdirSync(path.join(destDir, 'help'), { recursive: true });
|
||||
fs.writeFileSync(path.join(destDir, 'help', 'SKILL.md'), '# help\n');
|
||||
fs.writeFileSync(path.join(destDir, 'help', 'SKILL.md'), '# bare-stem help (#3664)\n');
|
||||
const siblingDir = path.join(configDir, 'skills', 'user-skill');
|
||||
fs.mkdirSync(siblingDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(siblingDir, 'SKILL.md'), '# user\n');
|
||||
|
||||
uninstallRuntimeArtifacts(runtime, configDir, 'global');
|
||||
|
||||
assert.ok(!fs.existsSync(destDir));
|
||||
assert.ok(fs.existsSync(path.join(siblingDir, 'SKILL.md')));
|
||||
// skills/gsd/ removed (gsd-* removed by _removeGsdEntries, bare-stem by legacy cleanup,
|
||||
// then DESCRIPTION.md removed, category dir removed as empty)
|
||||
assert.ok(!fs.existsSync(destDir), 'skills/gsd/ must be removed after uninstall');
|
||||
// User skill outside skills/gsd/ preserved
|
||||
assert.ok(fs.existsSync(path.join(siblingDir, 'SKILL.md')), 'user-skill must be preserved');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -436,7 +444,7 @@ describe('installRuntimeArtifacts — legacy migrations run before layout copy',
|
||||
assert.ok(fs.existsSync(path.join(configDir, 'skills', 'gsd-help', 'SKILL.md')));
|
||||
});
|
||||
|
||||
test('hermes: legacy flat skills/gsd-*/ migrated AND new nested skills/gsd/<stem>/ written', (t) => {
|
||||
test('hermes: legacy flat skills/gsd-*/ migrated AND new nested skills/gsd/gsd-<stem>/ written (#947)', (t) => {
|
||||
const configDir = createTempDir('gsd-legacy-hermes-install-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
@@ -446,13 +454,15 @@ describe('installRuntimeArtifacts — legacy migrations run before layout copy',
|
||||
|
||||
installRuntimeArtifacts('hermes', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.ok(!fs.existsSync(legacyFlatHelp));
|
||||
assert.ok(fs.existsSync(path.join(configDir, 'skills', 'gsd', 'help', 'SKILL.md')));
|
||||
assert.ok(!fs.existsSync(legacyFlatHelp), 'legacy flat skill must be removed');
|
||||
// #947: canonical path is skills/gsd/gsd-<stem>/ not skills/gsd/<stem>/
|
||||
assert.ok(fs.existsSync(path.join(configDir, 'skills', 'gsd', 'gsd-help', 'SKILL.md')),
|
||||
'skills/gsd/gsd-help/SKILL.md must exist after install (#947)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstallRuntimeArtifacts — legacy cleanup runs before layout removal', () => {
|
||||
test('hermes: both flat and nested layouts removed', (t) => {
|
||||
test('hermes: both flat and nested layouts removed (#947: bare-stem dirs cleaned on uninstall)', (t) => {
|
||||
const { uninstallRuntimeArtifacts } = require('../bin/install.js');
|
||||
const configDir = createTempDir('gsd-legacy-uninstall-hermes-');
|
||||
t.after(() => cleanup(configDir));
|
||||
@@ -463,8 +473,9 @@ describe('uninstallRuntimeArtifacts — legacy cleanup runs before layout remova
|
||||
fs.writeFileSync(path.join(flatHelp, 'SKILL.md'), '# legacy flat\n');
|
||||
|
||||
const nestedGsd = path.join(skillsDir, 'gsd');
|
||||
// Seed a pre-#947 bare-stem GSD skill (no gsd- prefix, from #3664 era)
|
||||
fs.mkdirSync(path.join(nestedGsd, 'help'), { recursive: true });
|
||||
fs.writeFileSync(path.join(nestedGsd, 'help', 'SKILL.md'), '# nested help\n');
|
||||
fs.writeFileSync(path.join(nestedGsd, 'help', 'SKILL.md'), '# nested help (bare-stem)\n');
|
||||
|
||||
const userSkill = path.join(skillsDir, 'user-skill');
|
||||
fs.mkdirSync(userSkill, { recursive: true });
|
||||
@@ -472,9 +483,12 @@ describe('uninstallRuntimeArtifacts — legacy cleanup runs before layout remova
|
||||
|
||||
uninstallRuntimeArtifacts('hermes', configDir, 'global');
|
||||
|
||||
assert.ok(!fs.existsSync(flatHelp));
|
||||
assert.ok(!fs.existsSync(nestedGsd));
|
||||
assert.ok(fs.existsSync(path.join(userSkill, 'SKILL.md')));
|
||||
// Pre-#2841 flat skills/gsd-help/ removed by legacy cleanup
|
||||
assert.ok(!fs.existsSync(flatHelp), 'flat gsd-help must be removed');
|
||||
// skills/gsd/ removed: bare-stem dirs cleaned + no gsd-* dirs remain → empty → removed
|
||||
assert.ok(!fs.existsSync(nestedGsd), 'skills/gsd/ must be removed after uninstall');
|
||||
// User content outside skills/gsd/ preserved
|
||||
assert.ok(fs.existsSync(path.join(userSkill, 'SKILL.md')), 'user-skill must be preserved');
|
||||
});
|
||||
|
||||
test('claude: legacy commands/gsd/ cleaned AND new skills/ entries removed', (t) => {
|
||||
|
||||
Reference in New Issue
Block a user