Files
msd-core/tests/surface-apply.test.cjs
Tom Boucher 4d578394eb refactor(3663): migrate applySurface to layout-driven loop; add skills kind to _syncGsdDir
- applySurface(runtimeConfigDir, commandsDir, agentsDir, manifest, clusterMap) → applySurface(runtimeConfigDir, layout, manifest, clusterMap)
- _syncGsdDir extended to handle skills kind (dirs not files, prefix-gated removal)
- _findInstallSource/_findAgentsSource deleted; listSurface now uses findInstallSourceRoot() from runtime-artifact-layout.cjs
- findInstallSourceRoot exported from runtime-artifact-layout.cjs
- test(3663): update surface-apply.test.cjs to new layout-passing shape + add skills kind test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 01:12:00 -04:00

224 lines
8.9 KiB
JavaScript

'use strict';
/**
* Tests for applySurface — file sync behavior.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { writeSurface, applySurface } = require('../get-shit-done/bin/lib/surface.cjs');
const { loadSkillsManifest, writeActiveProfile } = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { CLUSTERS } = require('../get-shit-done/bin/lib/clusters.cjs');
const { resolveRuntimeArtifactLayout } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const REAL_AGENTS_DIR = path.join(__dirname, '..', 'agents');
function tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-apply-'));
}
/**
* Create a minimal fixture install dir structure for claude/local layout.
* runtimeConfigDir is the layout configDir.
* commandsDir = runtimeConfigDir/commands/gsd
* agentsDir = runtimeConfigDir/agents
*/
function createFixtureRuntime() {
const base = tmpDir();
const runtimeConfigDir = base;
const commandsDir = path.join(runtimeConfigDir, 'commands', 'gsd');
const agentsDir = path.join(runtimeConfigDir, 'agents');
fs.mkdirSync(commandsDir, { recursive: true });
fs.mkdirSync(agentsDir, { recursive: true });
return { base, runtimeConfigDir, commandsDir, agentsDir };
}
describe('applySurface', () => {
test('core profile: only core skills appear in commandsDir', () => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
try {
writeActiveProfile(runtimeConfigDir, 'core');
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
// Every file should be a real stem we know about
for (const file of files) {
assert.ok(fs.existsSync(path.join(REAL_COMMANDS_DIR, file)), `unexpected file: ${file}`);
}
// At minimum core skills should be present
const coreStems = ['new-project', 'discuss-phase', 'plan-phase', 'execute-phase', 'help', 'update'];
for (const stem of coreStems) {
assert.ok(files.includes(`${stem}.md`), `core skill "${stem}" should be in commandsDir`);
}
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('removes superseded files when profile shrinks', () => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
try {
// Start with standard: put some skill files in commandsDir
writeActiveProfile(runtimeConfigDir, 'standard');
writeSurface(runtimeConfigDir, {
baseProfile: 'standard',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
const afterStandard = new Set(fs.readdirSync(commandsDir).filter(f => f.endsWith('.md')));
// Now switch to core: skills not in core should be removed
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
const afterCore = new Set(fs.readdirSync(commandsDir).filter(f => f.endsWith('.md')));
// core should be a subset of standard
assert.ok(afterCore.size <= afterStandard.size, 'core should have fewer or equal files than standard');
// Files removed should not be in core set
const coreStems = new Set(['new-project', 'discuss-phase', 'plan-phase', 'execute-phase', 'help', 'update']);
for (const file of afterCore) {
const stem = file.slice(0, -3);
assert.ok(
fs.existsSync(path.join(REAL_COMMANDS_DIR, file)),
`file in commandsDir not a real skill: ${file}`
);
}
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('leaves non-gsd .md files alone in agentsDir', () => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
try {
// Place a non-gsd agent file in agentsDir
const foreignAgent = path.join(agentsDir, 'my-custom-agent.md');
fs.writeFileSync(foreignAgent, '# custom agent\n', 'utf8');
writeActiveProfile(runtimeConfigDir, 'core');
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
// Non-gsd file should still be there
assert.ok(fs.existsSync(foreignAgent), 'non-gsd agent file should not be touched');
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('adds missing skill files from install source', () => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
try {
// commandsDir starts empty
writeActiveProfile(runtimeConfigDir, 'core');
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
// Core skills should now be present
assert.ok(
fs.existsSync(path.join(commandsDir, 'help.md')),
'help.md should be copied from install source'
);
assert.ok(
fs.existsSync(path.join(commandsDir, 'new-project.md')),
'new-project.md should be copied from install source'
);
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('_syncGsdDir skills kind: adds missing skill dirs, removes stale prefix-matched dirs, preserves foreign dirs', () => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const { stageSkillsForRuntimeAsSkills } = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { findInstallSourceRoot } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
// Minimal converter that produces SKILL.md with given stem
function converter(stem, content) {
return [
'---',
`name: ${stem}`,
'---',
content,
].join('\n');
}
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-skills-'));
try {
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
// Build a staged dir manually: gsd-help/SKILL.md and gsd-update/SKILL.md
const stem1 = 'gsd-help';
const stem2 = 'gsd-update';
fs.mkdirSync(path.join(stagedDir, stem1), { recursive: true });
fs.writeFileSync(path.join(stagedDir, stem1, 'SKILL.md'), '# help\n', 'utf8');
fs.mkdirSync(path.join(stagedDir, stem2), { recursive: true });
fs.writeFileSync(path.join(stagedDir, stem2, 'SKILL.md'), '# update\n', 'utf8');
// In destDir: stale gsd- dir + foreign user dir
const staleDir = path.join(destDir, 'gsd-old-skill');
fs.mkdirSync(staleDir, { recursive: true });
fs.writeFileSync(path.join(staleDir, 'SKILL.md'), '# old\n', 'utf8');
const foreignDir = path.join(destDir, 'my-custom-skill');
fs.mkdirSync(foreignDir, { recursive: true });
fs.writeFileSync(path.join(foreignDir, 'SKILL.md'), '# custom\n', 'utf8');
const skillsKind = { kind: 'skills', destSubpath: 'skills', prefix: 'gsd-', stage: () => stagedDir };
_syncGsdDir(stagedDir, destDir, skillsKind);
// staged dirs copied
assert.ok(fs.existsSync(path.join(destDir, stem1, 'SKILL.md')), 'gsd-help/SKILL.md should be copied');
assert.ok(fs.existsSync(path.join(destDir, stem2, 'SKILL.md')), 'gsd-update/SKILL.md should be copied');
// stale gsd- dir removed
assert.ok(!fs.existsSync(staleDir), 'stale gsd-old-skill dir should be removed');
// foreign dir preserved
assert.ok(fs.existsSync(foreignDir), 'my-custom-skill dir should be preserved');
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
});