refactor(tests): consolidate Runtime Artifact Layout Module — 12 files → 3 (#3759)

* refactor(tests): consolidate Runtime Artifact Layout Module — 12 files → 3

Closes #3757

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(changeset): add proper frontmatter to 3757 fragment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-20 20:39:09 -04:00
committed by GitHub
parent 3d52f5ee46
commit 1926353b5e
15 changed files with 1640 additions and 1822 deletions

View File

@@ -0,0 +1,8 @@
---
type: Fixed
pr: 3759
---
<!-- docs-exempt: internal test refactor only — no user-facing surface changed -->
Consolidates the Runtime Artifact Layout Module test cluster (ADR-3660) from 12 files spanning three module names (`surface-*`, `runtime-artifact-layout-*`, `install-profiles-*`) into 3 files, resolving the three-module-name split identified in ADR-3660.

View File

@@ -1,121 +0,0 @@
'use strict';
/**
* Tests for loadSkillsManifest — parses requires: frontmatter from commands/gsd/*.md
* and returns a Map<stem, string[]>.
*/
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 {
loadSkillsManifest,
} = require('../get-shit-done/bin/lib/install-profiles.cjs');
function createFixtureDir() {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-manifest-fixture-'));
return tmp;
}
function writeSkill(dir, stem, frontmatter) {
const content = `---\n${frontmatter}\n---\n\n# body\n`;
fs.writeFileSync(path.join(dir, `${stem}.md`), content);
}
describe('loadSkillsManifest', () => {
test('returns a Map', () => {
const dir = createFixtureDir();
try {
const m = loadSkillsManifest(dir);
assert.ok(m instanceof Map, 'should return a Map');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with no requires: frontmatter maps to empty array', () => {
const dir = createFixtureDir();
try {
writeSkill(dir, 'help', 'name: gsd:help\ndescription: Help text');
const m = loadSkillsManifest(dir);
assert.ok(m.has('help'), 'help should be in manifest');
assert.deepStrictEqual(m.get('help'), []);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: single value maps to array of one', () => {
const dir = createFixtureDir();
try {
writeSkill(dir, 'add-tests', 'name: gsd:add-tests\ndescription: Add tests\nrequires: [phase]');
const m = loadSkillsManifest(dir);
assert.ok(m.has('add-tests'));
assert.deepStrictEqual(m.get('add-tests'), ['phase']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: multiple values maps to full array', () => {
const dir = createFixtureDir();
try {
writeSkill(dir, 'plan-phase', 'name: gsd:plan-phase\ndescription: Plan\nrequires: [discuss-phase, phase, review, update]');
const m = loadSkillsManifest(dir);
assert.deepStrictEqual(m.get('plan-phase'), ['discuss-phase', 'phase', 'review', 'update']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('ignores non-.md files in the dir', () => {
const dir = createFixtureDir();
try {
writeSkill(dir, 'help', 'name: gsd:help\ndescription: Help');
fs.writeFileSync(path.join(dir, 'README.txt'), 'not a skill');
fs.writeFileSync(path.join(dir, 'notes.json'), '{}');
const m = loadSkillsManifest(dir);
assert.ok(m.has('help'));
assert.ok(!m.has('README'));
assert.ok(!m.has('notes'));
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('empty dir returns empty Map', () => {
const dir = createFixtureDir();
try {
const m = loadSkillsManifest(dir);
assert.strictEqual(m.size, 0);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: empty array maps to empty array', () => {
const dir = createFixtureDir();
try {
writeSkill(dir, 'explore', 'name: gsd:explore\ndescription: Explore\nrequires: []');
const m = loadSkillsManifest(dir);
assert.deepStrictEqual(m.get('explore'), []);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('loads real commands/gsd/ directory without throwing', () => {
const realDir = path.join(__dirname, '..', 'commands', 'gsd');
const m = loadSkillsManifest(realDir);
assert.ok(m.size >= 60, `expected >=60 skills, got ${m.size}`);
// discuss-phase requires [config, phase]
const depsDP = m.get('discuss-phase');
assert.ok(Array.isArray(depsDP), 'discuss-phase should be in manifest');
assert.ok(depsDP.includes('phase'), 'discuss-phase should require phase');
assert.ok(depsDP.includes('config'), 'discuss-phase should require config');
// help has no requires
assert.deepStrictEqual(m.get('help'), []);
});
});

View File

@@ -1,118 +0,0 @@
'use strict';
/**
* Tests for readActiveProfile / writeActiveProfile marker persistence.
*/
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 {
readActiveProfile,
writeActiveProfile,
} = require('../get-shit-done/bin/lib/install-profiles.cjs');
describe('readActiveProfile / writeActiveProfile', () => {
test('write then read round-trips the profile name', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
writeActiveProfile(dir, 'standard');
assert.strictEqual(readActiveProfile(dir), 'standard');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips "core" profile', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
writeActiveProfile(dir, 'core');
assert.strictEqual(readActiveProfile(dir), 'core');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips composed profiles "core,audit"', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
writeActiveProfile(dir, 'core,audit');
assert.strictEqual(readActiveProfile(dir), 'core,audit');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips "full"', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
writeActiveProfile(dir, 'full');
assert.strictEqual(readActiveProfile(dir), 'full');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('missing marker file returns null (not throws)', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('non-existent directory returns null (not throws)', () => {
const ghost = path.join(os.tmpdir(), 'gsd-marker-no-exist-' + Date.now());
const result = readActiveProfile(ghost);
assert.strictEqual(result, null);
});
test('corrupt marker content (invalid chars) returns null', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
fs.writeFileSync(path.join(dir, '.gsd-profile'), 'profile with spaces and !!!\n');
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('empty marker file returns null', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
fs.writeFileSync(path.join(dir, '.gsd-profile'), '');
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('writeActiveProfile creates the directory if it does not exist', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-base-'));
const nested = path.join(base, 'skills', '.claude');
try {
writeActiveProfile(nested, 'standard');
assert.ok(fs.existsSync(nested), 'directory should be created');
assert.strictEqual(readActiveProfile(nested), 'standard');
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('overwrites a previously written profile', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-marker-'));
try {
writeActiveProfile(dir, 'core');
writeActiveProfile(dir, 'full');
assert.strictEqual(readActiveProfile(dir), 'full');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

View File

@@ -1,196 +0,0 @@
'use strict';
/**
* Tests for resolveProfile — computes transitive closure over the requires: graph.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const path = require('path');
const {
PROFILES,
resolveProfile,
loadSkillsManifest,
} = require('../get-shit-done/bin/lib/install-profiles.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
describe('PROFILES map', () => {
test('PROFILES is frozen', () => {
assert.ok(Object.isFrozen(PROFILES));
});
test('PROFILES has core, standard, full keys', () => {
assert.ok('core' in PROFILES, 'PROFILES.core missing');
assert.ok('standard' in PROFILES, 'PROFILES.standard missing');
assert.ok('full' in PROFILES, 'PROFILES.full missing');
});
test('PROFILES.core contains the 7 main-loop skills (including phase)', () => {
const core = PROFILES.core;
assert.ok(Array.isArray(core), 'core should be an array');
const sorted = [...core].sort();
assert.deepStrictEqual(sorted, [
'discuss-phase',
'execute-phase',
'help',
'new-project',
'phase',
'plan-phase',
'update',
]);
});
test('PROFILES.full is the sentinel "*"', () => {
assert.strictEqual(PROFILES.full, '*');
});
test('PROFILES.standard contains at least the core skills', () => {
const core = new Set(PROFILES.core);
const standard = PROFILES.standard;
assert.ok(Array.isArray(standard), 'standard should be an array');
for (const s of core) {
assert.ok(standard.includes(s), `standard should include core skill: ${s}`);
}
});
test('PROFILES.standard has at least 10 skills', () => {
assert.ok(PROFILES.standard.length >= 10, `standard should have >=10 skills, got ${PROFILES.standard.length}`);
});
});
describe('resolveProfile', () => {
test('defaults to full when called with no args', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ manifest });
assert.strictEqual(result.name, 'full');
assert.strictEqual(result.skills, '*');
});
test('resolves core profile — returns 7+ skills', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['core'], manifest });
assert.strictEqual(result.name, 'core');
assert.ok(result.skills instanceof Set, 'skills should be a Set');
// core has 7 base skills.
assert.ok(result.skills.size >= 7, `core closure should have >=7 skills, got ${result.skills.size}`);
// All base skills must be present
for (const s of PROFILES.core) {
assert.ok(result.skills.has(s), `core closure should include ${s}`);
}
assert.ok(result.skills.has('phase'), 'core closure must include phase');
});
test('resolves standard profile — returns superset of core', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const coreResult = resolveProfile({ modes: ['core'], manifest });
const stdResult = resolveProfile({ modes: ['standard'], manifest });
assert.strictEqual(stdResult.name, 'standard');
assert.ok(stdResult.skills instanceof Set);
assert.ok(stdResult.skills.size >= coreResult.skills.size, 'standard should have >= skills than core');
// All core closure skills must be in standard
for (const s of coreResult.skills) {
assert.ok(stdResult.skills.has(s), `standard must include core skill: ${s}`);
}
});
test('resolves full profile — returns sentinel', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['full'], manifest });
assert.strictEqual(result.name, 'full');
assert.strictEqual(result.skills, '*');
});
test('composable profiles — core,standard union is same as standard', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const stdResult = resolveProfile({ modes: ['standard'], manifest });
const composed = resolveProfile({ modes: ['core', 'standard'], manifest });
// name should reflect composed
assert.ok(composed.name.includes('core') && composed.name.includes('standard'),
`composed name should include both, got: ${composed.name}`);
// skills union should equal standard (since core ⊂ standard)
for (const s of stdResult.skills) {
assert.ok(composed.skills.has(s), `composed should include standard skill: ${s}`);
}
});
test('transitive closure: skill that requires phase pulls in phase', () => {
// Build a minimal manifest: only discuss-phase requiring phase
const manifest = new Map([
['discuss-phase', ['phase']],
['phase', []],
['help', []],
]);
// Profile with only discuss-phase in base
const miniProfiles = { core: ['discuss-phase', 'help'], full: '*', standard: ['discuss-phase', 'help'] };
const result = resolveProfile({ modes: ['core'], manifest, _profilesOverride: miniProfiles });
assert.ok(result.skills.has('phase'), 'phase should be pulled in via closure from discuss-phase');
assert.ok(result.skills.has('discuss-phase'));
assert.ok(result.skills.has('help'));
});
test('deep transitive closure works (A→B→C pulls in C)', () => {
const manifest = new Map([
['a', ['b']],
['b', ['c']],
['c', []],
]);
const miniProfiles = { core: ['a'], full: '*', standard: ['a'] };
const result = resolveProfile({ modes: ['core'], manifest, _profilesOverride: miniProfiles });
assert.ok(result.skills.has('a'));
assert.ok(result.skills.has('b'));
assert.ok(result.skills.has('c'));
});
test('resolveProfile result has agents Set', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['core'], manifest });
assert.ok(result.agents instanceof Set, 'result should have agents Set');
});
test('resolveProfile standard — agents Set is non-empty (plan-phase pulls gsd-planner etc)', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['standard'], manifest });
assert.ok(result.agents instanceof Set, 'agents should be a Set');
assert.ok(result.agents.size > 0, `standard profile should have >0 agents, got ${result.agents.size}`);
// plan-phase is in standard and calls gsd-planner, gsd-plan-checker, gsd-phase-researcher
assert.ok(result.agents.has('gsd-planner'), 'standard should include gsd-planner (called by plan-phase)');
assert.ok(result.agents.has('gsd-plan-checker'), 'standard should include gsd-plan-checker (called by plan-phase)');
});
test('resolveProfile full — agents is empty Set (full staging uses srcDir directly)', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['full'], manifest });
assert.strictEqual(result.skills, '*');
// Full profile: agents Set is empty because stageAgentsForProfile uses srcDir directly
assert.ok(result.agents instanceof Set, 'agents should still be a Set for full');
});
test('agents are derived from skill body text — synthetic manifest', () => {
// Build a synthetic manifest where plan-phase calls gsd-planner
const manifest = new Map([
['plan-phase', []],
['phase', []],
]);
// Override with calls_agents map
manifest.get('plan-phase'); // ensure it exists
// Use the real loadSkillsManifest with REAL dir to verify body parsing works
const realManifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const planPhaseAgents = realManifest.get('_calls_agents_plan-phase') ||
// calls_agents may be stored as a separate key or on the manifest entry itself
[];
// This test validates that the real manifest has agent mappings for plan-phase
// by checking resolveProfile computes agents correctly
const result = resolveProfile({ modes: ['standard'], manifest: realManifest });
assert.ok(result.agents.has('gsd-planner'), 'gsd-planner should be derived from plan-phase body');
});
test('agents transitively closed — skill requiring plan-phase also gets its agents', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
// quick requires plan-phase (via requires: field or direct) and also calls gsd-planner directly
// new-project requires plan-phase so inherits its agents
const result = resolveProfile({ modes: ['standard'], manifest });
// Since plan-phase is in standard, and plan-phase calls gsd-planner, gsd-planner must be present
assert.ok(result.agents.has('gsd-planner'));
});
});

View File

@@ -1,271 +0,0 @@
'use strict';
/**
* Tests for stageSkillsForProfile and stageAgentsForProfile.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const {
stageSkillsForProfile,
stageAgentsForProfile,
stageSkillsForRuntimeAsSkills,
cleanupStagedSkills,
resolveProfile,
loadSkillsManifest,
STAGED_DIRS,
} = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const REAL_AGENTS_DIR = path.join(__dirname, '..', 'agents');
function createFixtureSkillsDir() {
const tmp = createTempDir('gsd-stage-profile-');
for (const name of ['plan-phase', 'execute-phase', 'autonomous', 'progress', 'help', 'phase']) {
fs.writeFileSync(path.join(tmp, `${name}.md`), `# ${name}\n`);
}
return tmp;
}
function createFixtureAgentsDir() {
const tmp = createTempDir('gsd-agents-profile-');
for (const name of ['gsd-planner', 'gsd-executor', 'gsd-code-reviewer']) {
fs.writeFileSync(path.join(tmp, `${name}.md`), `# ${name}\n`);
}
return tmp;
}
describe('stageSkillsForRuntimeAsSkills', () => {
test('is exported as a function', () => {
assert.strictEqual(typeof stageSkillsForRuntimeAsSkills, 'function');
});
test('registers stagedDir in STAGED_DIRS after staging', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'alpha.md'), '# alpha\n');
cleanupStagedSkills();
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'gsd-');
assert.ok(STAGED_DIRS.has(stagedDir), 'stagedDir must be in STAGED_DIRS');
});
test('non-existent srcCommandsDir returns srcCommandsDir unchanged', () => {
const ghost = path.join(require('os').tmpdir(), 'gsd-rta-no-exist-' + Date.now());
const converter = (content, _skillName) => content;
const result = stageSkillsForRuntimeAsSkills(ghost, { skills: '*' }, converter, 'gsd-');
assert.strictEqual(result, ghost);
});
test('empty prefix produces <stem>/SKILL.md without prefix segment', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'phase.md'), '# phase\n');
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, '');
const entries = fs.readdirSync(stagedDir);
assert.deepStrictEqual(entries, ['phase']);
const content = fs.readFileSync(path.join(stagedDir, 'phase', 'SKILL.md'), 'utf8');
assert.strictEqual(content, '# phase\n');
});
test('converter is called with (content, skillName) for each kept skill', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'alpha.md'), '# alpha\n');
fs.writeFileSync(path.join(src, 'beta.md'), '# beta\n');
const calls = [];
const converter = (content, skillName) => {
calls.push([content, skillName]);
return content;
};
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'x-');
assert.strictEqual(calls.length, 2);
const callMap = Object.fromEntries(calls.map(([c, n]) => [n, c]));
assert.strictEqual(callMap['x-alpha'], '# alpha\n');
assert.strictEqual(callMap['x-beta'], '# beta\n');
});
test('skills Set filters: only matching stems land in stagedDir', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
for (const name of ['alpha', 'beta', 'phase']) {
fs.writeFileSync(path.join(src, `${name}.md`), `# ${name}\n`);
}
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: new Set(['phase']) }, converter, 'gsd-');
const entries = fs.readdirSync(stagedDir).sort();
assert.deepStrictEqual(entries, ['gsd-phase']);
});
test('skills === "*" stages all md files as <prefix><stem>/SKILL.md', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
for (const name of ['alpha', 'beta', 'gamma']) {
fs.writeFileSync(path.join(src, `${name}.md`), `# ${name}\n`);
}
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'gsd-');
const entries = fs.readdirSync(stagedDir).sort();
assert.deepStrictEqual(entries, ['gsd-alpha', 'gsd-beta', 'gsd-gamma']);
for (const name of ['alpha', 'beta', 'gamma']) {
const content = fs.readFileSync(path.join(stagedDir, `gsd-${name}`, 'SKILL.md'), 'utf8');
assert.strictEqual(content, `# ${name}\n`);
}
});
});
describe('stageSkillsForProfile', () => {
test('full profile (skills === "*") returns srcDir unchanged', (t) => {
const src = createFixtureSkillsDir();
t.after(() => cleanup(src));
const result = stageSkillsForProfile(src, { skills: '*', agents: new Set() });
assert.strictEqual(result, src);
});
test('profile with Set copies only member files', (t) => {
const src = createFixtureSkillsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const skills = new Set(['plan-phase', 'help', 'phase']);
staged = stageSkillsForProfile(src, { skills, agents: new Set() });
assert.notStrictEqual(staged, src);
const files = fs.readdirSync(staged).sort();
assert.deepStrictEqual(files, ['help.md', 'phase.md', 'plan-phase.md']);
});
test('preserves file content byte-for-byte', (t) => {
const src = createFixtureSkillsDir();
const content = '# plan-phase special content\n\nsome body\n';
fs.writeFileSync(path.join(src, 'plan-phase.md'), content);
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const skills = new Set(['plan-phase']);
staged = stageSkillsForProfile(src, { skills, agents: new Set() });
const copied = fs.readFileSync(path.join(staged, 'plan-phase.md'), 'utf8');
assert.strictEqual(copied, content);
});
test('non-existent srcDir returns srcDir unchanged', () => {
const ghost = path.join(require('os').tmpdir(), 'gsd-no-exist-' + Date.now());
const result = stageSkillsForProfile(ghost, { skills: new Set(['help']), agents: new Set() });
assert.strictEqual(result, ghost);
});
test('empty skills Set produces empty staged dir', (t) => {
const src = createFixtureSkillsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
staged = stageSkillsForProfile(src, { skills: new Set(), agents: new Set() });
const files = fs.readdirSync(staged);
assert.deepStrictEqual(files, []);
});
});
describe('stageAgentsForProfile', () => {
test('full profile (skills === "*") returns srcDir unchanged', (t) => {
const src = createFixtureAgentsDir();
t.after(() => cleanup(src));
const result = stageAgentsForProfile(src, { skills: '*', agents: new Set() });
assert.strictEqual(result, src);
});
test('non-full profile with empty agents Set produces empty staged dir', (t) => {
const src = createFixtureAgentsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
staged = stageAgentsForProfile(src, { skills: new Set(['help']), agents: new Set() });
const files = fs.readdirSync(staged);
assert.deepStrictEqual(files, [], 'no agents for non-full profile by default');
});
test('non-full profile with agents Set copies only member agent files', (t) => {
const src = createFixtureAgentsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const agents = new Set(['gsd-planner']);
staged = stageAgentsForProfile(src, { skills: new Set(['plan-phase']), agents });
const files = fs.readdirSync(staged).sort();
assert.deepStrictEqual(files, ['gsd-planner.md']);
});
test('non-existent srcAgentsDir returns srcAgentsDir unchanged', () => {
const ghost = path.join(require('os').tmpdir(), 'gsd-agents-no-exist-' + Date.now());
const result = stageAgentsForProfile(ghost, { skills: new Set(), agents: new Set() });
assert.strictEqual(result, ghost);
});
test('standard profile — stageAgentsForProfile copies exactly the agents in resolvedProfile.agents', (t) => {
// Uses the real agents dir and commands dir
if (!fs.existsSync(REAL_AGENTS_DIR) || !fs.existsSync(REAL_COMMANDS_DIR)) return;
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const resolved = resolveProfile({ modes: ['standard'], manifest });
assert.ok(resolved.agents instanceof Set && resolved.agents.size > 0,
'standard profile must have >0 agents (plan-phase calls gsd-planner etc)');
let staged;
t.after(() => {
if (staged) cleanupStagedSkills();
});
staged = stageAgentsForProfile(REAL_AGENTS_DIR, resolved);
const stagedFiles = new Set(
fs.readdirSync(staged).filter(f => f.endsWith('.md')).map(f => f.slice(0, -3))
);
// Every file staged must be in resolved.agents
for (const stem of stagedFiles) {
assert.ok(resolved.agents.has(stem), `staged agent ${stem} not in resolved.agents`);
}
// Every agent in resolved.agents that exists in the real dir must be staged
for (const agentStem of resolved.agents) {
const exists = fs.existsSync(path.join(REAL_AGENTS_DIR, `${agentStem}.md`));
if (exists) {
assert.ok(stagedFiles.has(agentStem), `resolved agent ${agentStem} missing from staged dir`);
}
}
});
test('full profile staging returns real agents dir unchanged', () => {
if (!fs.existsSync(REAL_AGENTS_DIR)) return;
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const resolved = resolveProfile({ modes: ['full'], manifest });
const result = stageAgentsForProfile(REAL_AGENTS_DIR, resolved);
assert.strictEqual(result, REAL_AGENTS_DIR);
});
});

View File

@@ -1,94 +0,0 @@
'use strict';
/**
* Edge-case tests for resolveRuntimeArtifactLayout.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const { resolveRuntimeArtifactLayout } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
describe('hermes nested layout', () => {
test('hermes has destSubpath skills/gsd and empty prefix', () => {
const layout = resolveRuntimeArtifactLayout('hermes', '/tmp/x');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills/gsd');
assert.strictEqual(layout.kinds[0].prefix, '');
});
});
describe('cline empty kinds', () => {
test('cline has no kinds', () => {
const layout = resolveRuntimeArtifactLayout('cline', '/tmp/x');
assert.strictEqual(layout.kinds.length, 0);
});
});
describe('gemini commands layout', () => {
test('gemini has one commands kind', () => {
const layout = resolveRuntimeArtifactLayout('gemini', '/tmp/x');
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'commands');
});
});
describe('claude scope=local has commands and agents', () => {
test('claude local has both commands and agents kinds', () => {
const layout = resolveRuntimeArtifactLayout('claude', '/tmp/x', 'local');
const kindNames = layout.kinds.map(k => k.kind);
assert.ok(kindNames.includes('commands'), 'should have commands kind');
assert.ok(kindNames.includes('agents'), 'should have agents kind');
});
});
describe('claude scope=global has only skills', () => {
test('claude global has only skills kind', () => {
const layout = resolveRuntimeArtifactLayout('claude', '/tmp/x', 'global');
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
});
});
describe('unknown runtime throws TypeError', () => {
test('grok throws TypeError containing "grok"', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('grok', '/tmp/x'),
(err) => {
assert.ok(err instanceof TypeError);
assert.ok(err.message.includes('grok'), 'error message must contain the runtime name');
return true;
}
);
});
test('xyzunknown throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('xyzunknown', '/tmp/x'),
TypeError
);
});
});
describe('invalid configDir throws TypeError', () => {
test('empty configDir throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', ''),
TypeError
);
});
test('non-string configDir throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', null),
TypeError
);
});
});
describe('invalid scope throws TypeError', () => {
test('bad scope throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', '/x', 'invalid'),
TypeError
);
});
});

View File

@@ -0,0 +1,659 @@
'use strict';
/**
* Consolidated tests for the Runtime Artifact Layout Module — install-profiles parity (ADR-3660).
*
* Covers:
* - stageSkillsForProfile / stageAgentsForProfile / stageSkillsForRuntimeAsSkills
* - resolveProfile — transitive closure + PROFILES map
* - loadSkillsManifest — frontmatter parsing
* - readActiveProfile / writeActiveProfile — marker persistence
*
* Sources consolidated (4 files deleted):
* tests/install-profiles-stage.test.cjs
* tests/install-profiles-resolve.test.cjs
* tests/install-profiles-manifest.test.cjs
* tests/install-profiles-marker.test.cjs
*/
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 {
stageSkillsForProfile,
stageAgentsForProfile,
stageSkillsForRuntimeAsSkills,
cleanupStagedSkills,
resolveProfile,
loadSkillsManifest,
readActiveProfile,
writeActiveProfile,
PROFILES,
STAGED_DIRS,
} = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const REAL_AGENTS_DIR = path.join(__dirname, '..', 'agents');
// ─── helpers ────────────────────────────────────────────────────────────────
function tmpDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix || 'gsd-ip-'));
}
function createFixtureSkillsDir() {
const tmp = createTempDir('gsd-stage-profile-');
for (const name of ['plan-phase', 'execute-phase', 'autonomous', 'progress', 'help', 'phase']) {
fs.writeFileSync(path.join(tmp, `${name}.md`), `# ${name}\n`);
}
return tmp;
}
function createFixtureAgentsDir() {
const tmp = createTempDir('gsd-agents-profile-');
for (const name of ['gsd-planner', 'gsd-executor', 'gsd-code-reviewer']) {
fs.writeFileSync(path.join(tmp, `${name}.md`), `# ${name}\n`);
}
return tmp;
}
function writeSkill(dir, stem, frontmatter) {
const content = `---\n${frontmatter}\n---\n\n# body\n`;
fs.writeFileSync(path.join(dir, `${stem}.md`), content);
}
// ─── stageSkillsForRuntimeAsSkills ──────────────────────────────────────────
describe('stageSkillsForRuntimeAsSkills', () => {
test('is exported as a function', () => {
assert.strictEqual(typeof stageSkillsForRuntimeAsSkills, 'function');
});
test('registers stagedDir in STAGED_DIRS after staging', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'alpha.md'), '# alpha\n');
cleanupStagedSkills();
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'gsd-');
assert.ok(STAGED_DIRS.has(stagedDir), 'stagedDir must be in STAGED_DIRS');
});
test('non-existent srcCommandsDir returns srcCommandsDir unchanged', () => {
const ghost = path.join(os.tmpdir(), 'gsd-rta-no-exist-' + Date.now());
const converter = (content, _skillName) => content;
const result = stageSkillsForRuntimeAsSkills(ghost, { skills: '*' }, converter, 'gsd-');
assert.strictEqual(result, ghost);
});
test('empty prefix produces <stem>/SKILL.md without prefix segment', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'phase.md'), '# phase\n');
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, '');
const entries = fs.readdirSync(stagedDir);
assert.deepStrictEqual(entries, ['phase']);
const content = fs.readFileSync(path.join(stagedDir, 'phase', 'SKILL.md'), 'utf8');
assert.strictEqual(content, '# phase\n');
});
test('converter is called with (content, skillName) for each kept skill', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
fs.writeFileSync(path.join(src, 'alpha.md'), '# alpha\n');
fs.writeFileSync(path.join(src, 'beta.md'), '# beta\n');
const calls = [];
const converter = (content, skillName) => {
calls.push([content, skillName]);
return content;
};
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'x-');
assert.strictEqual(calls.length, 2);
const callMap = Object.fromEntries(calls.map(([c, n]) => [n, c]));
assert.strictEqual(callMap['x-alpha'], '# alpha\n');
assert.strictEqual(callMap['x-beta'], '# beta\n');
});
test('skills Set filters: only matching stems land in stagedDir', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
for (const name of ['alpha', 'beta', 'phase']) {
fs.writeFileSync(path.join(src, `${name}.md`), `# ${name}\n`);
}
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: new Set(['phase']) }, converter, 'gsd-');
const entries = fs.readdirSync(stagedDir).sort();
assert.deepStrictEqual(entries, ['gsd-phase']);
});
test('skills === "*" stages all md files as <prefix><stem>/SKILL.md', (t) => {
const src = createTempDir('gsd-rta-src-');
let stagedDir;
t.after(() => {
cleanup(src);
if (stagedDir) cleanupStagedSkills();
});
for (const name of ['alpha', 'beta', 'gamma']) {
fs.writeFileSync(path.join(src, `${name}.md`), `# ${name}\n`);
}
const converter = (content, _skillName) => content;
stagedDir = stageSkillsForRuntimeAsSkills(src, { skills: '*' }, converter, 'gsd-');
const entries = fs.readdirSync(stagedDir).sort();
assert.deepStrictEqual(entries, ['gsd-alpha', 'gsd-beta', 'gsd-gamma']);
for (const name of ['alpha', 'beta', 'gamma']) {
const content = fs.readFileSync(path.join(stagedDir, `gsd-${name}`, 'SKILL.md'), 'utf8');
assert.strictEqual(content, `# ${name}\n`);
}
});
});
// ─── stageSkillsForProfile ───────────────────────────────────────────────────
describe('stageSkillsForProfile', () => {
test('full profile (skills === "*") returns srcDir unchanged', (t) => {
const src = createFixtureSkillsDir();
t.after(() => cleanup(src));
const result = stageSkillsForProfile(src, { skills: '*', agents: new Set() });
assert.strictEqual(result, src);
});
test('profile with Set copies only member files', (t) => {
const src = createFixtureSkillsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const skills = new Set(['plan-phase', 'help', 'phase']);
staged = stageSkillsForProfile(src, { skills, agents: new Set() });
assert.notStrictEqual(staged, src);
const files = fs.readdirSync(staged).sort();
assert.deepStrictEqual(files, ['help.md', 'phase.md', 'plan-phase.md']);
});
test('preserves file content byte-for-byte', (t) => {
const src = createFixtureSkillsDir();
const content = '# plan-phase special content\n\nsome body\n';
fs.writeFileSync(path.join(src, 'plan-phase.md'), content);
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const skills = new Set(['plan-phase']);
staged = stageSkillsForProfile(src, { skills, agents: new Set() });
const copied = fs.readFileSync(path.join(staged, 'plan-phase.md'), 'utf8');
assert.strictEqual(copied, content);
});
test('non-existent srcDir returns srcDir unchanged', () => {
const ghost = path.join(os.tmpdir(), 'gsd-no-exist-' + Date.now());
const result = stageSkillsForProfile(ghost, { skills: new Set(['help']), agents: new Set() });
assert.strictEqual(result, ghost);
});
test('empty skills Set produces empty staged dir', (t) => {
const src = createFixtureSkillsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
staged = stageSkillsForProfile(src, { skills: new Set(), agents: new Set() });
const files = fs.readdirSync(staged);
assert.deepStrictEqual(files, []);
});
});
// ─── stageAgentsForProfile ───────────────────────────────────────────────────
describe('stageAgentsForProfile', () => {
test('full profile (skills === "*") returns srcDir unchanged', (t) => {
const src = createFixtureAgentsDir();
t.after(() => cleanup(src));
const result = stageAgentsForProfile(src, { skills: '*', agents: new Set() });
assert.strictEqual(result, src);
});
test('non-full profile with empty agents Set produces empty staged dir', (t) => {
const src = createFixtureAgentsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
staged = stageAgentsForProfile(src, { skills: new Set(['help']), agents: new Set() });
const files = fs.readdirSync(staged);
assert.deepStrictEqual(files, [], 'no agents for non-full profile by default');
});
test('non-full profile with agents Set copies only member agent files', (t) => {
const src = createFixtureAgentsDir();
let staged;
t.after(() => {
cleanup(src);
if (staged) cleanupStagedSkills();
});
const agents = new Set(['gsd-planner']);
staged = stageAgentsForProfile(src, { skills: new Set(['plan-phase']), agents });
const files = fs.readdirSync(staged).sort();
assert.deepStrictEqual(files, ['gsd-planner.md']);
});
test('non-existent srcAgentsDir returns srcAgentsDir unchanged', () => {
const ghost = path.join(os.tmpdir(), 'gsd-agents-no-exist-' + Date.now());
const result = stageAgentsForProfile(ghost, { skills: new Set(), agents: new Set() });
assert.strictEqual(result, ghost);
});
test('standard profile — stageAgentsForProfile copies exactly the agents in resolvedProfile.agents', (t) => {
if (!fs.existsSync(REAL_AGENTS_DIR) || !fs.existsSync(REAL_COMMANDS_DIR)) return;
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const resolved = resolveProfile({ modes: ['standard'], manifest });
assert.ok(resolved.agents instanceof Set && resolved.agents.size > 0,
'standard profile must have >0 agents (plan-phase calls gsd-planner etc)');
let staged;
t.after(() => {
if (staged) cleanupStagedSkills();
});
staged = stageAgentsForProfile(REAL_AGENTS_DIR, resolved);
const stagedFiles = new Set(
fs.readdirSync(staged).filter(f => f.endsWith('.md')).map(f => f.slice(0, -3))
);
for (const stem of stagedFiles) {
assert.ok(resolved.agents.has(stem), `staged agent ${stem} not in resolved.agents`);
}
for (const agentStem of resolved.agents) {
const exists = fs.existsSync(path.join(REAL_AGENTS_DIR, `${agentStem}.md`));
if (exists) {
assert.ok(stagedFiles.has(agentStem), `resolved agent ${agentStem} missing from staged dir`);
}
}
});
test('full profile staging returns real agents dir unchanged', () => {
if (!fs.existsSync(REAL_AGENTS_DIR)) return;
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const resolved = resolveProfile({ modes: ['full'], manifest });
const result = stageAgentsForProfile(REAL_AGENTS_DIR, resolved);
assert.strictEqual(result, REAL_AGENTS_DIR);
});
});
// ─── PROFILES map + resolveProfile ──────────────────────────────────────────
describe('PROFILES map', () => {
test('PROFILES is frozen', () => {
assert.ok(Object.isFrozen(PROFILES));
});
test('PROFILES has core, standard, full keys', () => {
assert.ok('core' in PROFILES, 'PROFILES.core missing');
assert.ok('standard' in PROFILES, 'PROFILES.standard missing');
assert.ok('full' in PROFILES, 'PROFILES.full missing');
});
test('PROFILES.core contains the 7 main-loop skills (including phase)', () => {
const core = PROFILES.core;
assert.ok(Array.isArray(core), 'core should be an array');
const sorted = [...core].sort();
assert.deepStrictEqual(sorted, [
'discuss-phase',
'execute-phase',
'help',
'new-project',
'phase',
'plan-phase',
'update',
]);
});
test('PROFILES.full is the sentinel "*"', () => {
assert.strictEqual(PROFILES.full, '*');
});
test('PROFILES.standard contains at least the core skills', () => {
const core = new Set(PROFILES.core);
const standard = PROFILES.standard;
assert.ok(Array.isArray(standard), 'standard should be an array');
for (const s of core) {
assert.ok(standard.includes(s), `standard should include core skill: ${s}`);
}
});
test('PROFILES.standard has at least 10 skills', () => {
assert.ok(PROFILES.standard.length >= 10, `standard should have >=10 skills, got ${PROFILES.standard.length}`);
});
});
describe('resolveProfile', () => {
test('defaults to full when called with no modes arg', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ manifest });
assert.strictEqual(result.name, 'full');
assert.strictEqual(result.skills, '*');
});
test('resolves core profile — returns 7+ skills, all base stems present', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['core'], manifest });
assert.strictEqual(result.name, 'core');
assert.ok(result.skills instanceof Set, 'skills should be a Set');
assert.ok(result.skills.size >= 7, `core closure should have >=7 skills, got ${result.skills.size}`);
for (const s of PROFILES.core) {
assert.ok(result.skills.has(s), `core closure should include ${s}`);
}
assert.ok(result.skills.has('phase'), 'core closure must include phase');
});
test('resolves standard profile — superset of core', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const coreResult = resolveProfile({ modes: ['core'], manifest });
const stdResult = resolveProfile({ modes: ['standard'], manifest });
assert.strictEqual(stdResult.name, 'standard');
assert.ok(stdResult.skills instanceof Set);
assert.ok(stdResult.skills.size >= coreResult.skills.size, 'standard should have >= skills than core');
for (const s of coreResult.skills) {
assert.ok(stdResult.skills.has(s), `standard must include core skill: ${s}`);
}
});
test('resolves full profile — returns sentinel', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['full'], manifest });
assert.strictEqual(result.name, 'full');
assert.strictEqual(result.skills, '*');
});
test('composable profiles — core,standard union is same as standard', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const stdResult = resolveProfile({ modes: ['standard'], manifest });
const composed = resolveProfile({ modes: ['core', 'standard'], manifest });
assert.ok(composed.name.includes('core') && composed.name.includes('standard'),
`composed name should include both, got: ${composed.name}`);
for (const s of stdResult.skills) {
assert.ok(composed.skills.has(s), `composed should include standard skill: ${s}`);
}
});
test('transitive closure: skill that requires phase pulls in phase', () => {
const manifest = new Map([
['discuss-phase', ['phase']],
['phase', []],
['help', []],
]);
const miniProfiles = { core: ['discuss-phase', 'help'], full: '*', standard: ['discuss-phase', 'help'] };
const result = resolveProfile({ modes: ['core'], manifest, _profilesOverride: miniProfiles });
assert.ok(result.skills.has('phase'), 'phase should be pulled in via closure from discuss-phase');
assert.ok(result.skills.has('discuss-phase'));
assert.ok(result.skills.has('help'));
});
test('deep transitive closure works (A→B→C pulls in C)', () => {
const manifest = new Map([
['a', ['b']],
['b', ['c']],
['c', []],
]);
const miniProfiles = { core: ['a'], full: '*', standard: ['a'] };
const result = resolveProfile({ modes: ['core'], manifest, _profilesOverride: miniProfiles });
assert.ok(result.skills.has('a'));
assert.ok(result.skills.has('b'));
assert.ok(result.skills.has('c'));
});
test('result has agents Set', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['core'], manifest });
assert.ok(result.agents instanceof Set, 'result should have agents Set');
});
test('standard — agents Set is non-empty; gsd-planner and gsd-plan-checker present', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['standard'], manifest });
assert.ok(result.agents instanceof Set, 'agents should be a Set');
assert.ok(result.agents.size > 0, `standard profile should have >0 agents, got ${result.agents.size}`);
assert.ok(result.agents.has('gsd-planner'), 'standard should include gsd-planner');
assert.ok(result.agents.has('gsd-plan-checker'), 'standard should include gsd-plan-checker');
});
test('full — agents is a Set (full staging uses srcDir directly)', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['full'], manifest });
assert.strictEqual(result.skills, '*');
assert.ok(result.agents instanceof Set, 'agents should still be a Set for full');
});
test('agents are derived from real manifest body text (gsd-planner from plan-phase)', () => {
const realManifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['standard'], manifest: realManifest });
assert.ok(result.agents.has('gsd-planner'), 'gsd-planner should be derived from plan-phase body');
});
test('agents transitively closed — plan-phase in standard brings its agents', () => {
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = resolveProfile({ modes: ['standard'], manifest });
assert.ok(result.agents.has('gsd-planner'));
});
});
// ─── loadSkillsManifest ──────────────────────────────────────────────────────
describe('loadSkillsManifest', () => {
test('returns a Map', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
const m = loadSkillsManifest(dir);
assert.ok(m instanceof Map, 'should return a Map');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with no requires: frontmatter maps to empty array', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
writeSkill(dir, 'help', 'name: gsd:help\ndescription: Help text');
const m = loadSkillsManifest(dir);
assert.ok(m.has('help'), 'help should be in manifest');
assert.deepStrictEqual(m.get('help'), []);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: single value maps to array of one', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
writeSkill(dir, 'add-tests', 'name: gsd:add-tests\ndescription: Add tests\nrequires: [phase]');
const m = loadSkillsManifest(dir);
assert.ok(m.has('add-tests'));
assert.deepStrictEqual(m.get('add-tests'), ['phase']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: multiple values maps to full array', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
writeSkill(dir, 'plan-phase', 'name: gsd:plan-phase\ndescription: Plan\nrequires: [discuss-phase, phase, review, update]');
const m = loadSkillsManifest(dir);
assert.deepStrictEqual(m.get('plan-phase'), ['discuss-phase', 'phase', 'review', 'update']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('ignores non-.md files in the dir', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
writeSkill(dir, 'help', 'name: gsd:help\ndescription: Help');
fs.writeFileSync(path.join(dir, 'README.txt'), 'not a skill');
fs.writeFileSync(path.join(dir, 'notes.json'), '{}');
const m = loadSkillsManifest(dir);
assert.ok(m.has('help'));
assert.ok(!m.has('README'));
assert.ok(!m.has('notes'));
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('empty dir returns empty Map', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
const m = loadSkillsManifest(dir);
assert.strictEqual(m.size, 0);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('skill with requires: empty array maps to empty array', () => {
const dir = tmpDir('gsd-manifest-fixture-');
try {
writeSkill(dir, 'explore', 'name: gsd:explore\ndescription: Explore\nrequires: []');
const m = loadSkillsManifest(dir);
assert.deepStrictEqual(m.get('explore'), []);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('loads real commands/gsd/ directory: >=60 skills, discuss-phase deps correct, help has no requires', () => {
const m = loadSkillsManifest(REAL_COMMANDS_DIR);
// Use prefix/set assertion, not a hardcoded count — avoids stale-count anti-pattern
assert.ok(m.size >= 60, `expected >=60 skills, got ${m.size}`);
const depsDP = m.get('discuss-phase');
assert.ok(Array.isArray(depsDP), 'discuss-phase should be in manifest');
assert.ok(depsDP.includes('phase'), 'discuss-phase should require phase');
assert.ok(depsDP.includes('config'), 'discuss-phase should require config');
assert.deepStrictEqual(m.get('help'), []);
});
});
// ─── readActiveProfile / writeActiveProfile ──────────────────────────────────
describe('readActiveProfile / writeActiveProfile', () => {
test('write then read round-trips the profile name', () => {
const dir = tmpDir('gsd-marker-');
try {
writeActiveProfile(dir, 'standard');
assert.strictEqual(readActiveProfile(dir), 'standard');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips "core" profile', () => {
const dir = tmpDir('gsd-marker-');
try {
writeActiveProfile(dir, 'core');
assert.strictEqual(readActiveProfile(dir), 'core');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips composed profiles "core,audit"', () => {
const dir = tmpDir('gsd-marker-');
try {
writeActiveProfile(dir, 'core,audit');
assert.strictEqual(readActiveProfile(dir), 'core,audit');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips "full"', () => {
const dir = tmpDir('gsd-marker-');
try {
writeActiveProfile(dir, 'full');
assert.strictEqual(readActiveProfile(dir), 'full');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('missing marker file returns null (not throws)', () => {
const dir = tmpDir('gsd-marker-');
try {
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('non-existent directory returns null (not throws)', () => {
const ghost = path.join(os.tmpdir(), 'gsd-marker-no-exist-' + Date.now());
const result = readActiveProfile(ghost);
assert.strictEqual(result, null);
});
test('corrupt marker content (invalid chars) returns null', () => {
const dir = tmpDir('gsd-marker-');
try {
fs.writeFileSync(path.join(dir, '.gsd-profile'), 'profile with spaces and !!!\n');
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('empty marker file returns null', () => {
const dir = tmpDir('gsd-marker-');
try {
fs.writeFileSync(path.join(dir, '.gsd-profile'), '');
const result = readActiveProfile(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('writeActiveProfile creates the directory if it does not exist', () => {
const base = tmpDir('gsd-marker-base-');
const nested = path.join(base, 'skills', '.claude');
try {
writeActiveProfile(nested, 'standard');
assert.ok(fs.existsSync(nested), 'directory should be created');
assert.strictEqual(readActiveProfile(nested), 'standard');
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
test('overwrites a previously written profile', () => {
const dir = tmpDir('gsd-marker-');
try {
writeActiveProfile(dir, 'core');
writeActiveProfile(dir, 'full');
assert.strictEqual(readActiveProfile(dir), 'full');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

View File

@@ -1,109 +0,0 @@
'use strict';
/**
* Tests for resolveRuntimeArtifactLayout kind.stage() invocations.
* Verifies each kind type produces the expected staged directory structure.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { resolveRuntimeArtifactLayout } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
// A small resolved profile selecting known real skills
const CORE_SKILLS = new Set(['help', 'phase', 'new-project']);
const CORE_AGENTS = new Set(['gsd-planner']);
const PROFILE_CORE = { skills: CORE_SKILLS, agents: CORE_AGENTS };
const PROFILE_FULL = { skills: '*', agents: new Set() };
const FAKE_DIR = '/tmp/fake-config-dir-stage';
describe('commands kind — stage (gemini)', () => {
test('stage returns a directory containing only the selected skill .md files', () => {
const layout = resolveRuntimeArtifactLayout('gemini', FAKE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
const stagedDir = commandsKind.stage(PROFILE_CORE);
// stagedDir is managed by stageSkillsForProfile — just verify, no manual cleanup needed
const entries = fs.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
// All staged files must come from the selected skill set
for (const entry of entries) {
const stem = entry.slice(0, -3);
assert.ok(CORE_SKILLS.has(stem), `unexpected skill staged: ${stem}`);
}
// At least one file must be present (help.md exists in real commands/gsd)
assert.ok(entries.length >= 1, 'at least one skill file should be staged');
});
});
describe('agents kind — stage (claude local)', () => {
test('stage returns a directory containing only the selected agent .md files', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_DIR, 'local');
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
assert.ok(agentsKind, 'should have an agents kind');
const stagedDir = agentsKind.stage(PROFILE_CORE);
// stagedDir may be empty (agents Set from core profile may be empty set)
// But it must be a valid directory
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
assert.ok(fs.statSync(stagedDir).isDirectory(), 'stagedDir must be a directory');
});
});
describe('skills kind — stage (claude global)', () => {
test('stage returns a directory containing gsd-<stem>/SKILL.md entries', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_DIR, 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
const stagedDir = skillsKind.stage(PROFILE_CORE);
// managed by stageSkillsForRuntimeAsSkills — no manual cleanup needed
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir);
// Each entry should be a directory named gsd-<stem>
for (const entry of entries) {
assert.ok(entry.startsWith('gsd-'), `entry should start with gsd-: ${entry}`);
const skillMd = path.join(stagedDir, entry, 'SKILL.md');
assert.ok(fs.existsSync(skillMd), `SKILL.md must exist in ${entry}`);
}
assert.ok(entries.length >= 1, 'at least one skill dir should be staged');
});
});
describe('skills kind — stage with full profile', () => {
test('stage with skills="*" stages all commands/gsd/*.md as skills', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_DIR, 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
const stagedDir = skillsKind.stage(PROFILE_FULL);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir);
// Full profile: should have many skills
assert.ok(entries.length > 10, `full profile should have many skills, got ${entries.length}`);
for (const entry of entries) {
assert.ok(entry.startsWith('gsd-'), `entry should start with gsd-: ${entry}`);
const skillMd = path.join(stagedDir, entry, 'SKILL.md');
assert.ok(fs.existsSync(skillMd), `SKILL.md must exist in ${entry}`);
}
});
});
describe('opencode commands kind — stage', () => {
test('opencode stage returns directory with .md files for selected skills', () => {
const layout = resolveRuntimeArtifactLayout('opencode', FAKE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
const stagedDir = commandsKind.stage(PROFILE_CORE);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
for (const entry of entries) {
const stem = entry.slice(0, -3);
assert.ok(CORE_SKILLS.has(stem), `unexpected skill staged: ${stem}`);
}
});
});

View File

@@ -0,0 +1,795 @@
'use strict';
/**
* Runtime Artifact Layout Module (ADR-3660) — surface seam.
* Consolidated from: surface-apply, surface-resolve, surface-state,
* surface-clusters, surface-list (5 files deleted).
* See also: runtime-artifact-layout.test.cjs, runtime-artifact-layout-install-profiles.test.cjs
*/
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, readSurface, resolveSurface, listSurface, applySurface } = require('../get-shit-done/bin/lib/surface.cjs');
const { loadSkillsManifest, writeActiveProfile, resolveProfile } = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { resolveRuntimeArtifactLayout } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
const { CLUSTERS, allClusteredSkills } = require('../get-shit-done/bin/lib/clusters.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
// ─── helpers ────────────────────────────────────────────────────────────────
function tmpDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix || 'gsd-ral-surf-'));
}
function createFixtureRuntime() {
const base = createTempDir('gsd-surface-apply-');
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 };
}
function realManifest() {
return loadSkillsManifest(REAL_COMMANDS_DIR);
}
function readFrontmatterDescription(markdown) {
const lines = markdown.split('\n');
if (lines[0].trim() !== '---') return '';
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (line.trim() === '---') break;
const sep = line.indexOf(':');
if (sep === -1) continue;
const key = line.slice(0, sep).trim();
if (key !== 'description') continue;
return line.slice(sep + 1).trim();
}
return '';
}
// ─── applySurface ────────────────────────────────────────────────────────────
describe('applySurface', () => {
test('core profile: only core skills appear in commandsDir', (t) => {
const { base, runtimeConfigDir, commandsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
writeActiveProfile(runtimeConfigDir, 'core');
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
const resolved = applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
for (const file of files) {
assert.ok(fs.existsSync(path.join(REAL_COMMANDS_DIR, file)), `unexpected file: ${file}`);
}
const expectedCore = [...resolved.skills].map(stem => `${stem}.md`).sort();
assert.deepStrictEqual(
[...files].sort(),
expectedCore,
'commandsDir should contain exactly core commands'
);
});
test('removes superseded files when profile shrinks', (t) => {
const { base, runtimeConfigDir, commandsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
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')));
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const resolvedCore = applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
const afterCore = new Set(fs.readdirSync(commandsDir).filter(f => f.endsWith('.md')));
assert.ok(afterCore.size <= afterStandard.size, 'core should have fewer or equal files than standard');
const expectedCore = [...resolvedCore.skills].map(stem => `${stem}.md`).sort();
assert.deepStrictEqual(
[...afterCore].sort(),
expectedCore,
'afterCore should contain exactly core commands'
);
for (const file of afterCore) {
assert.ok(
fs.existsSync(path.join(REAL_COMMANDS_DIR, file)),
`file in commandsDir not a real skill: ${file}`
);
}
});
test('leaves non-gsd .md files alone in agentsDir', (t) => {
const { base, runtimeConfigDir, agentsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
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);
assert.ok(fs.existsSync(foreignAgent), 'non-gsd agent file should not be touched');
});
test('adds missing skill files from install source', (t) => {
const { base, runtimeConfigDir, commandsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
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);
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'
);
});
test('_syncGsdDir skills kind: adds missing skill dirs, removes stale prefix-matched dirs, preserves foreign dirs', (t) => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const base = createTempDir('gsd-surface-skills-');
t.after(() => cleanup(base));
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
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');
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);
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');
assert.ok(!fs.existsSync(staleDir), 'stale gsd-old-skill dir should be removed');
assert.ok(fs.existsSync(foreignDir), 'my-custom-skill dir should be preserved');
});
test('applySurface recreates missing destination directories', (t) => {
const base = createTempDir('gsd-surface-missing-dest-');
t.after(() => cleanup(base));
const runtimeConfigDir = base;
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 commandsDir = path.join(runtimeConfigDir, 'commands', 'gsd');
assert.ok(fs.existsSync(commandsDir), 'commands/gsd dir should be created even if initially absent');
const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
assert.ok(files.length > 0, 'commands/gsd should contain staged skill files');
assert.ok(files.includes('help.md'), 'help.md should be present after applySurface on missing dest');
});
test('Hermes profile shrink: stale GSD skill dirs are removed; user skills preserved', (t) => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const base = createTempDir('gsd-surface-hermes-shrink-');
t.after(() => cleanup(base));
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
fs.mkdirSync(path.join(stagedDir, 'gsd-executor'), { recursive: true });
fs.writeFileSync(path.join(stagedDir, 'gsd-executor', 'SKILL.md'), '# executor\n', 'utf8');
fs.mkdirSync(path.join(destDir, 'gsd-executor'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'gsd-executor', 'SKILL.md'), '# executor\n', 'utf8');
fs.mkdirSync(path.join(destDir, 'gsd-planner'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'gsd-planner', 'SKILL.md'), '# planner\n', 'utf8');
fs.mkdirSync(path.join(destDir, 'user-skill'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'user-skill', 'SKILL.md'), '# user\n', 'utf8');
const manifest = new Map([
['gsd-executor', []],
['gsd-planner', []],
]);
const hermesKind = { kind: 'skills', destSubpath: 'skills/gsd', prefix: '', stage: () => stagedDir };
_syncGsdDir(stagedDir, destDir, hermesKind, manifest);
assert.ok(
fs.existsSync(path.join(destDir, 'gsd-executor', 'SKILL.md')),
'gsd-executor should be kept (in staged set)'
);
assert.ok(
!fs.existsSync(path.join(destDir, 'gsd-planner')),
'gsd-planner should be removed (in manifest but not in staged set — stale GSD skill)'
);
assert.ok(
fs.existsSync(path.join(destDir, 'user-skill', 'SKILL.md')),
'user-skill should be preserved (not in manifest — user-owned)'
);
});
test('_syncGsdDir skills kind (hermes): preserves non-GSD user dir under skills/gsd/ when kindPrefix is empty', (t) => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const base = createTempDir('gsd-surface-hermes-');
t.after(() => cleanup(base));
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
const stem1 = 'help';
fs.mkdirSync(path.join(stagedDir, stem1), { recursive: true });
fs.writeFileSync(path.join(stagedDir, stem1, 'SKILL.md'), '# help\n', 'utf8');
const userDir = path.join(destDir, 'user-custom-skill');
fs.mkdirSync(userDir, { recursive: true });
fs.writeFileSync(path.join(userDir, 'SKILL.md'), '# user custom\n', 'utf8');
const hermesKind = { kind: 'skills', destSubpath: 'skills/gsd', prefix: '', stage: () => stagedDir };
_syncGsdDir(stagedDir, destDir, hermesKind);
assert.ok(fs.existsSync(userDir), 'user-custom-skill dir must be preserved when kindPrefix is empty (Hermes)');
assert.ok(fs.existsSync(path.join(destDir, stem1, 'SKILL.md')), 'GSD help/SKILL.md must be copied');
});
});
// ─── resolveSurface ──────────────────────────────────────────────────────────
describe('resolveSurface', () => {
test('no surface state + core base profile → identical to resolveProfile core', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeActiveProfile(dir, 'core');
const manifest = realManifest();
const surfaceResolved = resolveSurface(dir, manifest, CLUSTERS);
const profileResolved = resolveProfile({ modes: ['core'], manifest });
assert.ok(surfaceResolved.skills instanceof Set);
assert.ok(profileResolved.skills instanceof Set);
assert.deepStrictEqual(
[...surfaceResolved.skills].sort(),
[...profileResolved.skills].sort(),
'surface with no state should equal profile resolution'
);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('standard base + disabledClusters:["utility"] removes utility skills', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeActiveProfile(dir, 'standard');
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
for (const stem of CLUSTERS.utility) {
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
if (standardResolved.skills.has(stem)) {
assert.ok(
!resolved.skills.has(stem),
`"${stem}" should be removed by disabling utility cluster`
);
}
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('explicitAdds:["sketch"] adds sketch to a core install', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeActiveProfile(dir, 'core');
writeSurface(dir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: ['sketch'],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
assert.ok(resolved.skills.has('sketch'), 'sketch must be in resolved skills');
const sketchRequires = manifest.get('sketch') || [];
for (const dep of sketchRequires) {
assert.ok(resolved.skills.has(dep), `transitive dep "${dep}" of sketch must be present`);
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('explicitRemoves removes individual skill stems', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: ['progress'],
});
writeActiveProfile(dir, 'standard');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(!resolved.skills.has('progress'), '"progress" must be removed by explicitRemoves');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('result is a Set<string> with name property and agents Set', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeActiveProfile(dir, 'core');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
assert.ok(typeof resolved.name === 'string');
assert.ok(resolved.agents instanceof Set);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('surface with baseProfile overrides .gsd-profile marker', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeActiveProfile(dir, 'core');
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
assert.deepStrictEqual(
[...resolved.skills].sort(),
[...standardResolved.skills].sort(),
'surface baseProfile takes precedence over marker'
);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('disabled cluster + explicitAdds can re-add specific skills from disabled cluster', () => {
const dir = tmpDir('gsd-surface-resolve-');
try {
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['workspace_state'],
explicitAdds: ['capture'],
explicitRemoves: [],
});
writeActiveProfile(dir, 'standard');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills.has('capture'), '"capture" must be present via explicitAdds');
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
for (const stem of CLUSTERS.workspace_state) {
if (stem === 'capture') continue;
if (standardResolved.skills.has(stem)) {
assert.ok(
!resolved.skills.has(stem),
`"${stem}" should be removed (workspace_state disabled, not explicitly re-added)`
);
}
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});
// ─── readSurface / writeSurface ──────────────────────────────────────────────
describe('readSurface / writeSurface', () => {
test('round-trips a complete surface state', () => {
const dir = tmpDir('gsd-surface-state-');
try {
const state = {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: ['sketch'],
explicitRemoves: [],
};
writeSurface(dir, state);
const read = readSurface(dir);
assert.deepStrictEqual(read, state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips empty arrays', () => {
const dir = tmpDir('gsd-surface-state-');
try {
const state = {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
};
writeSurface(dir, state);
assert.deepStrictEqual(readSurface(dir), state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips composed base profile', () => {
const dir = tmpDir('gsd-surface-state-');
try {
const state = {
baseProfile: 'core,audit',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: ['health'],
};
writeSurface(dir, state);
assert.deepStrictEqual(readSurface(dir), state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('missing file returns null', () => {
const dir = tmpDir('gsd-surface-state-');
try {
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('non-existent directory returns null', () => {
const ghost = path.join(os.tmpdir(), 'gsd-surface-no-exist-' + Date.now());
const result = readSurface(ghost);
assert.strictEqual(result, null);
});
test('corrupt JSON returns null', () => {
const dir = tmpDir('gsd-surface-state-');
try {
fs.writeFileSync(path.join(dir, '.gsd-surface.json'), '{not valid json', 'utf8');
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('JSON missing baseProfile field returns null', () => {
const dir = tmpDir('gsd-surface-state-');
try {
fs.writeFileSync(
path.join(dir, '.gsd-surface.json'),
JSON.stringify({ disabledClusters: [], explicitAdds: [], explicitRemoves: [] }),
'utf8'
);
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('JSON with non-array disabledClusters returns null', () => {
const dir = tmpDir('gsd-surface-state-');
try {
fs.writeFileSync(
path.join(dir, '.gsd-surface.json'),
JSON.stringify({ baseProfile: 'standard', disabledClusters: 'utility', explicitAdds: [], explicitRemoves: [] }),
'utf8'
);
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('atomic write: result file is never a partial tmp file', () => {
const dir = tmpDir('gsd-surface-state-');
try {
const state = { baseProfile: 'full', disabledClusters: [], explicitAdds: [], explicitRemoves: [] };
writeSurface(dir, state);
const files = fs.readdirSync(dir);
const tmpFiles = files.filter(f => f.includes('.tmp.'));
assert.deepStrictEqual(tmpFiles, [], 'no tmp files should remain after write');
assert.ok(files.includes('.gsd-surface.json'));
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('second write overwrites first', () => {
const dir = tmpDir('gsd-surface-state-');
try {
writeSurface(dir, { baseProfile: 'core', disabledClusters: [], explicitAdds: [], explicitRemoves: [] });
writeSurface(dir, { baseProfile: 'standard', disabledClusters: ['utility'], explicitAdds: [], explicitRemoves: [] });
const read = readSurface(dir);
assert.strictEqual(read.baseProfile, 'standard');
assert.deepStrictEqual(read.disabledClusters, ['utility']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('writeSurface creates directory if it does not exist', () => {
const base = tmpDir('gsd-surface-state-');
const nested = path.join(base, 'skills', 'subdir');
try {
writeSurface(nested, { baseProfile: 'full', disabledClusters: [], explicitAdds: [], explicitRemoves: [] });
assert.ok(fs.existsSync(nested));
assert.ok(readSurface(nested) !== null);
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
});
// ─── CLUSTERS data structure ─────────────────────────────────────────────────
describe('CLUSTERS data structure', () => {
test('no cluster is empty', () => {
for (const [name, members] of Object.entries(CLUSTERS)) {
assert.ok(members.length > 0, `cluster ${name} must not be empty`);
}
});
test('every cluster member is a real skill stem in commands/gsd/', () => {
const entries = fs.readdirSync(REAL_COMMANDS_DIR, { withFileTypes: true });
const realStems = new Set(
entries
.filter(e => e.isFile() && e.name.endsWith('.md'))
.map(e => e.name.slice(0, -3))
);
const mismatches = [];
for (const [cluster, members] of Object.entries(CLUSTERS)) {
for (const stem of members) {
if (!realStems.has(stem)) {
mismatches.push(`${cluster}: "${stem}" not found in commands/gsd/`);
}
}
}
assert.deepStrictEqual(mismatches, [], `Cluster members missing from disk:\n${mismatches.join('\n')}`);
});
test('union of all clusters covers every skill in commands/gsd/', () => {
const entries = fs.readdirSync(REAL_COMMANDS_DIR, { withFileTypes: true });
const realStems = new Set(
entries
.filter(e => e.isFile() && e.name.endsWith('.md'))
.map(e => e.name.slice(0, -3))
);
const clustered = allClusteredSkills();
const uncategorized = [];
for (const stem of realStems) {
if (!clustered.has(stem)) uncategorized.push(stem);
}
assert.deepStrictEqual(
uncategorized,
[],
`Uncategorized skills (not in any cluster):\n${uncategorized.sort().join('\n')}`
);
});
test('CLUSTERS is frozen (immutable)', () => {
assert.ok(Object.isFrozen(CLUSTERS), 'CLUSTERS must be frozen');
for (const [name, members] of Object.entries(CLUSTERS)) {
assert.ok(Object.isFrozen(members), `CLUSTERS.${name} must be frozen`);
}
});
test('cluster names contain the expected set from research memo §3.2', () => {
const expectedClusterNames = new Set([
'core_loop',
'audit_review',
'milestone',
'research_ideate',
'workspace_state',
'docs',
'ui',
'ai_eval',
'ns_meta',
'utility',
]);
const actualClusterNames = new Set(Object.keys(CLUSTERS));
for (const name of expectedClusterNames) {
assert.ok(actualClusterNames.has(name), `expected cluster "${name}" missing from CLUSTERS`);
}
});
test('allClusteredSkills returns a Set containing all cluster members', () => {
const result = allClusteredSkills();
assert.ok(result instanceof Set, 'allClusteredSkills() must return a Set');
for (const members of Object.values(CLUSTERS)) {
for (const stem of members) {
assert.ok(result.has(stem), `allClusteredSkills() missing "${stem}"`);
}
}
});
});
// ─── listSurface ─────────────────────────────────────────────────────────────
describe('listSurface', () => {
test('returns { enabled, disabled, tokenCost } structure', () => {
const dir = tmpDir('gsd-surface-list-');
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
assert.ok(Array.isArray(result.enabled), 'enabled must be array');
assert.ok(Array.isArray(result.disabled), 'disabled must be array');
assert.ok(typeof result.tokenCost === 'number', 'tokenCost must be number');
assert.ok(result.tokenCost >= 0, 'tokenCost must be non-negative');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('core profile: enabled has fewer skills than full; enabled + disabled = total stems', () => {
const dir = tmpDir('gsd-surface-list-');
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const coreList = listSurface(dir, manifest, CLUSTERS);
const totalStems = [...manifest.keys()].filter(k => !k.startsWith('_calls_agents_')).length;
assert.ok(
coreList.enabled.length < totalStems,
'core should enable fewer skills than total'
);
assert.ok(coreList.disabled.length > 0, 'core should have some disabled skills');
assert.ok(coreList.enabled.length + coreList.disabled.length === totalStems,
'enabled + disabled must equal total stems');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('disabling utility cluster reduces enabled count', () => {
const dir = tmpDir('gsd-surface-list-');
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'standard');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const beforeList = listSurface(dir, manifest, CLUSTERS);
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: [],
explicitRemoves: [],
});
const afterList = listSurface(dir, manifest, CLUSTERS);
assert.ok(afterList.enabled.length <= beforeList.enabled.length,
'disabling utility cluster should not increase enabled count');
assert.ok(afterList.tokenCost <= beforeList.tokenCost,
'disabling a cluster should not increase token cost');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('tokenCost is sum of description char lengths ÷ 4 for enabled skills', () => {
const dir = tmpDir('gsd-surface-list-');
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
let expected = 0;
for (const stem of result.enabled) {
const filePath = path.join(REAL_COMMANDS_DIR, `${stem}.md`);
if (!fs.existsSync(filePath)) continue;
const markdown = fs.readFileSync(filePath, 'utf8');
const description = readFrontmatterDescription(markdown);
if (description) expected += Math.ceil(description.length / 4);
}
assert.strictEqual(result.tokenCost, expected, 'tokenCost must equal sum of description lengths ÷ 4');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('enabled and disabled arrays are sorted', () => {
const dir = tmpDir('gsd-surface-list-');
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'standard');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
assert.deepStrictEqual(result.enabled, [...result.enabled].sort());
assert.deepStrictEqual(result.disabled, [...result.disabled].sort());
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

View File

@@ -1,15 +1,33 @@
'use strict';
/**
* Tests for resolveRuntimeArtifactLayout — structural shape per runtime.
* Consolidated tests for the Runtime Artifact Layout Module (ADR-3660) — layout seam.
*
* Covers:
* - resolveRuntimeArtifactLayout — structural shape per runtime
* - resolveRuntimeArtifactLayout edge-cases (error paths, invalid input)
* - kind.stage() invocations per kind type
*
* Sources consolidated (3 files deleted):
* tests/runtime-artifact-layout-resolve.test.cjs
* tests/runtime-artifact-layout-edge-cases.test.cjs
* tests/runtime-artifact-layout-stage.test.cjs
*
* See also:
* runtime-artifact-layout-surface.test.cjs — surface seam
* runtime-artifact-layout-install-profiles.test.cjs — install-profiles seam
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { resolveRuntimeArtifactLayout } = require('../get-shit-done/bin/lib/runtime-artifact-layout.cjs');
const FAKE_DIR = '/tmp/fake-config-dir';
// ─── resolveRuntimeArtifactLayout — structural shape ────────────────────────
describe('resolveRuntimeArtifactLayout — claude local', () => {
test('returns correct layout for claude scope=local', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_DIR, 'local');
@@ -217,3 +235,162 @@ describe('resolveRuntimeArtifactLayout — kilo', () => {
assert.strictEqual(typeof layout.kinds[0].stage, 'function');
});
});
// ─── resolveRuntimeArtifactLayout — edge-cases ──────────────────────────────
describe('resolveRuntimeArtifactLayout edge-cases', () => {
test('hermes has destSubpath skills/gsd and empty prefix', () => {
const layout = resolveRuntimeArtifactLayout('hermes', '/tmp/x');
assert.strictEqual(layout.kinds[0].destSubpath, 'skills/gsd');
assert.strictEqual(layout.kinds[0].prefix, '');
});
test('cline has no kinds', () => {
const layout = resolveRuntimeArtifactLayout('cline', '/tmp/x');
assert.strictEqual(layout.kinds.length, 0);
});
test('gemini has one commands kind', () => {
const layout = resolveRuntimeArtifactLayout('gemini', '/tmp/x');
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'commands');
});
test('claude local has both commands and agents kinds', () => {
const layout = resolveRuntimeArtifactLayout('claude', '/tmp/x', 'local');
const kindNames = layout.kinds.map(k => k.kind);
assert.ok(kindNames.includes('commands'), 'should have commands kind');
assert.ok(kindNames.includes('agents'), 'should have agents kind');
});
test('claude global has only skills kind', () => {
const layout = resolveRuntimeArtifactLayout('claude', '/tmp/x', 'global');
assert.strictEqual(layout.kinds.length, 1);
assert.strictEqual(layout.kinds[0].kind, 'skills');
});
test('unknown runtime grok throws TypeError containing runtime name', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('grok', '/tmp/x'),
(err) => {
assert.ok(err instanceof TypeError);
assert.ok(err.message.includes('grok'), 'error message must contain the runtime name');
return true;
}
);
});
test('unknown runtime xyzunknown throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('xyzunknown', '/tmp/x'),
TypeError
);
});
test('empty configDir throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', ''),
TypeError
);
});
test('non-string configDir throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', null),
TypeError
);
});
test('bad scope throws TypeError', () => {
assert.throws(
() => resolveRuntimeArtifactLayout('claude', '/x', 'invalid'),
TypeError
);
});
});
// ─── kind.stage() invocations ────────────────────────────────────────────────
const CORE_SKILLS = new Set(['help', 'phase', 'new-project']);
const CORE_AGENTS = new Set(['gsd-planner']);
const PROFILE_CORE = { skills: CORE_SKILLS, agents: CORE_AGENTS };
const PROFILE_FULL = { skills: '*', agents: new Set() };
const FAKE_STAGE_DIR = '/tmp/fake-config-dir-stage';
describe('stage — commands kind (gemini)', () => {
test('stage returns a directory containing only the selected skill .md files', () => {
const layout = resolveRuntimeArtifactLayout('gemini', FAKE_STAGE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
const stagedDir = commandsKind.stage(PROFILE_CORE);
const entries = fs.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
for (const entry of entries) {
const stem = entry.slice(0, -3);
assert.ok(CORE_SKILLS.has(stem), `unexpected skill staged: ${stem}`);
}
assert.ok(entries.length >= 1, 'at least one skill file should be staged');
});
});
describe('stage — agents kind (claude local)', () => {
test('stage returns a valid directory for the agents kind', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'local');
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
assert.ok(agentsKind, 'should have an agents kind');
const stagedDir = agentsKind.stage(PROFILE_CORE);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
assert.ok(fs.statSync(stagedDir).isDirectory(), 'stagedDir must be a directory');
});
});
describe('stage — skills kind (claude global)', () => {
test('stage returns a directory containing gsd-<stem>/SKILL.md entries', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
const stagedDir = skillsKind.stage(PROFILE_CORE);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir);
for (const entry of entries) {
assert.ok(entry.startsWith('gsd-'), `entry should start with gsd-: ${entry}`);
const skillMd = path.join(stagedDir, entry, 'SKILL.md');
assert.ok(fs.existsSync(skillMd), `SKILL.md must exist in ${entry}`);
}
assert.ok(entries.length >= 1, 'at least one skill dir should be staged');
});
test('stage with skills="*" stages all commands/gsd/*.md as skills', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
const stagedDir = skillsKind.stage(PROFILE_FULL);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir);
assert.ok(entries.length > 10, `full profile should have many skills, got ${entries.length}`);
for (const entry of entries) {
assert.ok(entry.startsWith('gsd-'), `entry should start with gsd-: ${entry}`);
const skillMd = path.join(stagedDir, entry, 'SKILL.md');
assert.ok(fs.existsSync(skillMd), `SKILL.md must exist in ${entry}`);
}
});
});
describe('stage — opencode commands kind', () => {
test('opencode stage returns directory with .md files for selected skills', () => {
const layout = resolveRuntimeArtifactLayout('opencode', FAKE_STAGE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
const stagedDir = commandsKind.stage(PROFILE_CORE);
assert.ok(fs.existsSync(stagedDir), 'stagedDir must exist');
const entries = fs.readdirSync(stagedDir).filter(f => f.endsWith('.md'));
for (const entry of entries) {
const stem = entry.slice(0, -3);
assert.ok(CORE_SKILLS.has(stem), `unexpected skill staged: ${stem}`);
}
});
});

View File

@@ -1,316 +0,0 @@
'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 { 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 { createTempDir, cleanup } = require('./helpers.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const REAL_AGENTS_DIR = path.join(__dirname, '..', 'agents');
/**
* 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 = createTempDir('gsd-surface-apply-');
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', (t) => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
writeActiveProfile(runtimeConfigDir, 'core');
writeSurface(runtimeConfigDir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const layout = resolveRuntimeArtifactLayout('claude', runtimeConfigDir, 'local');
const resolved = 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}`);
}
// Core profile should materialize exactly the resolved core command set.
const expectedCore = [...resolved.skills].map(stem => `${stem}.md`).sort();
assert.deepStrictEqual(
[...files].sort(),
expectedCore,
'commandsDir should contain exactly core commands'
);
});
test('removes superseded files when profile shrinks', (t) => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
// 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: [],
});
const resolvedCore = 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');
// Core profile should materialize exactly the resolved core command set.
const expectedCore = [...resolvedCore.skills].map(stem => `${stem}.md`).sort();
assert.deepStrictEqual(
[...afterCore].sort(),
expectedCore,
'afterCore should contain exactly core commands'
);
// All files should still map to known real skills.
for (const file of afterCore) {
assert.ok(
fs.existsSync(path.join(REAL_COMMANDS_DIR, file)),
`file in commandsDir not a real skill: ${file}`
);
}
});
test('leaves non-gsd .md files alone in agentsDir', (t) => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
// 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');
});
test('adds missing skill files from install source', (t) => {
const { base, runtimeConfigDir, commandsDir, agentsDir } = createFixtureRuntime();
t.after(() => cleanup(base));
// 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'
);
});
test('_syncGsdDir skills kind: adds missing skill dirs, removes stale prefix-matched dirs, preserves foreign dirs', (t) => {
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 = createTempDir('gsd-surface-skills-');
t.after(() => cleanup(base));
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');
});
test('applySurface recreates missing destination directories', (t) => {
// Fixture: layout configDir exists but the dest subdirectory for the kinds does NOT.
const base = createTempDir('gsd-surface-missing-dest-');
t.after(() => cleanup(base));
const runtimeConfigDir = base;
// Do NOT pre-create commands/gsd or agents — they are intentionally absent.
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);
// commands/gsd must have been created and populated
const commandsDir = path.join(runtimeConfigDir, 'commands', 'gsd');
assert.ok(fs.existsSync(commandsDir), 'commands/gsd dir should be created even if initially absent');
const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
assert.ok(files.length > 0, 'commands/gsd should contain staged skill files');
assert.ok(files.includes('help.md'), 'help.md should be present after applySurface on missing dest');
});
test('Hermes profile shrink: stale GSD skill dirs are removed; user skills preserved', (t) => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const base = createTempDir('gsd-surface-hermes-shrink-');
t.after(() => cleanup(base));
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
// Staged: only gsd-executor (profile shrunk — gsd-planner no longer in profile)
fs.mkdirSync(path.join(stagedDir, 'gsd-executor'), { recursive: true });
fs.writeFileSync(path.join(stagedDir, 'gsd-executor', 'SKILL.md'), '# executor\n', 'utf8');
// Dest already has: gsd-executor (keep), gsd-planner (stale GSD), user-skill (user-owned)
fs.mkdirSync(path.join(destDir, 'gsd-executor'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'gsd-executor', 'SKILL.md'), '# executor\n', 'utf8');
fs.mkdirSync(path.join(destDir, 'gsd-planner'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'gsd-planner', 'SKILL.md'), '# planner\n', 'utf8');
fs.mkdirSync(path.join(destDir, 'user-skill'), { recursive: true });
fs.writeFileSync(path.join(destDir, 'user-skill', 'SKILL.md'), '# user\n', 'utf8');
// Manifest contains gsd-executor and gsd-planner as canonical GSD skills.
// user-skill is NOT in manifest (user-owned).
const manifest = new Map([
['gsd-executor', []],
['gsd-planner', []],
]);
// Hermes kind: empty prefix, destSubpath = skills/gsd
const hermesKind = { kind: 'skills', destSubpath: 'skills/gsd', prefix: '', stage: () => stagedDir };
_syncGsdDir(stagedDir, destDir, hermesKind, manifest);
assert.ok(
fs.existsSync(path.join(destDir, 'gsd-executor', 'SKILL.md')),
'gsd-executor should be kept (in staged set)'
);
assert.ok(
!fs.existsSync(path.join(destDir, 'gsd-planner')),
'gsd-planner should be removed (in manifest but not in staged set — stale GSD skill)'
);
assert.ok(
fs.existsSync(path.join(destDir, 'user-skill', 'SKILL.md')),
'user-skill should be preserved (not in manifest — user-owned)'
);
});
test('_syncGsdDir skills kind (hermes): preserves non-GSD user dir under skills/gsd/ when kindPrefix is empty', (t) => {
const { _syncGsdDir } = require('../get-shit-done/bin/lib/surface.cjs');
const base = createTempDir('gsd-surface-hermes-');
t.after(() => cleanup(base));
const stagedDir = path.join(base, 'staged');
const destDir = path.join(base, 'dest');
fs.mkdirSync(destDir, { recursive: true });
// Staged contains a GSD skill named 'help' (no prefix under hermes skills/gsd/)
const stem1 = 'help';
fs.mkdirSync(path.join(stagedDir, stem1), { recursive: true });
fs.writeFileSync(path.join(stagedDir, stem1, 'SKILL.md'), '# help\n', 'utf8');
// Dest also has a user-owned custom skill dir (no gsd- prefix — Hermes namespace)
const userDir = path.join(destDir, 'user-custom-skill');
fs.mkdirSync(userDir, { recursive: true });
fs.writeFileSync(path.join(userDir, 'SKILL.md'), '# user custom\n', 'utf8');
// kindPrefix === '' simulates Hermes (destSubpath = skills/gsd, prefix = '')
const hermesKind = { kind: 'skills', destSubpath: 'skills/gsd', prefix: '', stage: () => stagedDir };
_syncGsdDir(stagedDir, destDir, hermesKind);
// The user's custom skill dir must be preserved — it's not in staged but should not be removed
// (Fix 4: when kindPrefix === '', skip the startsWith guard and preserve ALL non-staged dirs)
assert.ok(fs.existsSync(userDir), 'user-custom-skill dir must be preserved when kindPrefix is empty (Hermes)');
// The staged skill must still be copied
assert.ok(fs.existsSync(path.join(destDir, stem1, 'SKILL.md')), 'GSD help/SKILL.md must be copied');
});
});

View File

@@ -1,94 +0,0 @@
'use strict';
/**
* Tests for CLUSTERS data structure integrity.
* Verifies every cluster member is a real skill stem and all skills are covered.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { CLUSTERS, allClusteredSkills } = require('../get-shit-done/bin/lib/clusters.cjs');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
function realSkillStems() {
const entries = fs.readdirSync(COMMANDS_DIR, { withFileTypes: true });
return new Set(
entries
.filter(e => e.isFile() && e.name.endsWith('.md'))
.map(e => e.name.slice(0, -3))
);
}
describe('CLUSTERS data structure', () => {
test('no cluster is empty', () => {
for (const [name, members] of Object.entries(CLUSTERS)) {
assert.ok(members.length > 0, `cluster ${name} must not be empty`);
}
});
test('every cluster member is a real skill stem in commands/gsd/', () => {
const realStems = realSkillStems();
const mismatches = [];
for (const [cluster, members] of Object.entries(CLUSTERS)) {
for (const stem of members) {
if (!realStems.has(stem)) {
mismatches.push(`${cluster}: "${stem}" not found in commands/gsd/`);
}
}
}
assert.deepStrictEqual(mismatches, [], `Cluster members missing from disk:\n${mismatches.join('\n')}`);
});
test('union of all clusters covers every skill in commands/gsd/', () => {
const realStems = realSkillStems();
const clustered = allClusteredSkills();
const uncategorized = [];
for (const stem of realStems) {
if (!clustered.has(stem)) uncategorized.push(stem);
}
assert.deepStrictEqual(
uncategorized,
[],
`Uncategorized skills (not in any cluster):\n${uncategorized.sort().join('\n')}`
);
});
test('CLUSTERS is frozen (immutable)', () => {
assert.ok(Object.isFrozen(CLUSTERS), 'CLUSTERS must be frozen');
for (const [name, members] of Object.entries(CLUSTERS)) {
assert.ok(Object.isFrozen(members), `CLUSTERS.${name} must be frozen`);
}
});
test('cluster names match expected set from research memo §3.2', () => {
const expectedClusterNames = new Set([
'core_loop',
'audit_review',
'milestone',
'research_ideate',
'workspace_state',
'docs',
'ui',
'ai_eval',
'ns_meta',
'utility',
]);
const actualClusterNames = new Set(Object.keys(CLUSTERS));
for (const name of expectedClusterNames) {
assert.ok(actualClusterNames.has(name), `expected cluster "${name}" missing from CLUSTERS`);
}
});
test('allClusteredSkills returns a Set containing all cluster members', () => {
const result = allClusteredSkills();
assert.ok(result instanceof Set, 'allClusteredSkills() must return a Set');
for (const members of Object.values(CLUSTERS)) {
for (const stem of members) {
assert.ok(result.has(stem), `allClusteredSkills() missing "${stem}"`);
}
}
});
});

View File

@@ -1,143 +0,0 @@
'use strict';
/**
* Tests for listSurface — enabled/disabled/tokenCost output.
*/
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, listSurface } = 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 REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
function tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-list-'));
}
function readFrontmatterDescription(markdown) {
const lines = markdown.split('\n');
if (lines[0].trim() !== '---') return '';
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (line.trim() === '---') break;
const sep = line.indexOf(':');
if (sep === -1) continue;
const key = line.slice(0, sep).trim();
if (key !== 'description') continue;
return line.slice(sep + 1).trim();
}
return '';
}
describe('listSurface', () => {
test('returns { enabled, disabled, tokenCost } structure', () => {
const dir = tmpDir();
try {
// Write source marker so listSurface can find descriptions
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
assert.ok(Array.isArray(result.enabled), 'enabled must be array');
assert.ok(Array.isArray(result.disabled), 'disabled must be array');
assert.ok(typeof result.tokenCost === 'number', 'tokenCost must be number');
assert.ok(result.tokenCost >= 0, 'tokenCost must be non-negative');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('core profile: enabled has fewer skills than full', () => {
const dir = tmpDir();
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const coreList = listSurface(dir, manifest, CLUSTERS);
// Core should have fewer enabled skills than total
const totalStems = [...manifest.keys()].filter(k => !k.startsWith('_calls_agents_')).length;
assert.ok(
coreList.enabled.length < totalStems,
'core should enable fewer skills than total'
);
assert.ok(coreList.disabled.length > 0, 'core should have some disabled skills');
assert.ok(coreList.enabled.length + coreList.disabled.length === totalStems,
'enabled + disabled must equal total stems');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('disabling utility cluster reduces enabled count', () => {
const dir = tmpDir();
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'standard');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const beforeList = listSurface(dir, manifest, CLUSTERS);
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: [],
explicitRemoves: [],
});
const afterList = listSurface(dir, manifest, CLUSTERS);
assert.ok(afterList.enabled.length <= beforeList.enabled.length,
'disabling utility cluster should not increase enabled count');
assert.ok(afterList.tokenCost <= beforeList.tokenCost,
'disabling a cluster should not increase token cost');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('tokenCost is sum of description char lengths ÷ 4 for enabled skills', () => {
const dir = tmpDir();
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'core');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
// Manually compute expected token cost for enabled skills
let expected = 0;
for (const stem of result.enabled) {
const filePath = path.join(REAL_COMMANDS_DIR, `${stem}.md`);
if (!fs.existsSync(filePath)) continue;
const markdown = fs.readFileSync(filePath, 'utf8');
const description = readFrontmatterDescription(markdown);
if (description) expected += Math.ceil(description.length / 4);
}
assert.strictEqual(result.tokenCost, expected, 'tokenCost must equal sum of description lengths ÷ 4');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('enabled and disabled arrays are sorted', () => {
const dir = tmpDir();
try {
fs.writeFileSync(path.join(dir, '.gsd-source'), REAL_COMMANDS_DIR, 'utf8');
writeActiveProfile(dir, 'standard');
const manifest = loadSkillsManifest(REAL_COMMANDS_DIR);
const result = listSurface(dir, manifest, CLUSTERS);
assert.deepStrictEqual(result.enabled, [...result.enabled].sort());
assert.deepStrictEqual(result.disabled, [...result.disabled].sort());
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

View File

@@ -1,194 +0,0 @@
'use strict';
/**
* Tests for resolveSurface — profile + cluster + explicit combinations.
*/
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 { readSurface, writeSurface, resolveSurface } = require('../get-shit-done/bin/lib/surface.cjs');
const { resolveProfile, loadSkillsManifest, writeActiveProfile } = require('../get-shit-done/bin/lib/install-profiles.cjs');
const { CLUSTERS } = require('../get-shit-done/bin/lib/clusters.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
function tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-resolve-'));
}
function realManifest() {
return loadSkillsManifest(REAL_COMMANDS_DIR);
}
describe('resolveSurface', () => {
test('no surface state + core base profile → identical to resolveProfile core', () => {
const dir = tmpDir();
try {
writeActiveProfile(dir, 'core');
const manifest = realManifest();
const surfaceResolved = resolveSurface(dir, manifest, CLUSTERS);
const profileResolved = resolveProfile({ modes: ['core'], manifest });
// Both should have same skill sets
assert.ok(surfaceResolved.skills instanceof Set);
assert.ok(profileResolved.skills instanceof Set);
assert.deepStrictEqual(
[...surfaceResolved.skills].sort(),
[...profileResolved.skills].sort(),
'surface with no state should equal profile resolution'
);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('standard base + disabledClusters:["utility"] removes utility skills', () => {
const dir = tmpDir();
try {
writeActiveProfile(dir, 'standard');
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
// Utility cluster members should not be in the result
for (const stem of CLUSTERS.utility) {
// Only check stems that were actually in the standard profile
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
if (standardResolved.skills.has(stem)) {
assert.ok(
!resolved.skills.has(stem),
`"${stem}" should be removed by disabling utility cluster`
);
}
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('explicitAdds:["sketch"] adds sketch to a core install', () => {
const dir = tmpDir();
try {
writeActiveProfile(dir, 'core');
writeSurface(dir, {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: ['sketch'],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
assert.ok(resolved.skills.has('sketch'), 'sketch must be in resolved skills');
// Transitive requires of sketch should also be present
const sketchRequires = manifest.get('sketch') || [];
for (const dep of sketchRequires) {
assert.ok(resolved.skills.has(dep), `transitive dep "${dep}" of sketch must be present`);
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('explicitRemoves removes individual skill stems', () => {
const dir = tmpDir();
try {
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: ['progress'],
});
writeActiveProfile(dir, 'standard');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
// standard includes 'progress', so removing it should take it out
assert.ok(!resolved.skills.has('progress'), '"progress" must be removed by explicitRemoves');
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('result is a Set<string> with name property', () => {
const dir = tmpDir();
try {
writeActiveProfile(dir, 'core');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
assert.ok(resolved.skills instanceof Set);
assert.ok(typeof resolved.name === 'string');
assert.ok(resolved.agents instanceof Set);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('surface with baseProfile overrides .gsd-profile marker', () => {
const dir = tmpDir();
try {
writeActiveProfile(dir, 'core');
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
});
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
// Should use standard profile from surface state, not core from marker
assert.deepStrictEqual(
[...resolved.skills].sort(),
[...standardResolved.skills].sort(),
'surface baseProfile takes precedence over marker'
);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('disabled cluster + explicitAdds can re-add specific skills from disabled cluster', () => {
const dir = tmpDir();
try {
writeSurface(dir, {
baseProfile: 'standard',
disabledClusters: ['workspace_state'],
explicitAdds: ['capture'],
explicitRemoves: [],
});
writeActiveProfile(dir, 'standard');
const manifest = realManifest();
const resolved = resolveSurface(dir, manifest, CLUSTERS);
// workspace_state is disabled, but capture is explicitly re-added
assert.ok(resolved.skills.has('capture'), '"capture" must be present via explicitAdds');
// Other workspace_state members that were in standard should be gone
const standardResolved = resolveProfile({ modes: ['standard'], manifest });
for (const stem of CLUSTERS.workspace_state) {
if (stem === 'capture') continue;
if (standardResolved.skills.has(stem)) {
assert.ok(
!resolved.skills.has(stem),
`"${stem}" should be removed (workspace_state disabled, not explicitly re-added)`
);
}
}
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

View File

@@ -1,165 +0,0 @@
'use strict';
/**
* Tests for readSurface / writeSurface — state IO round-trips.
*/
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 { readSurface, writeSurface } = require('../get-shit-done/bin/lib/surface.cjs');
function tmpDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-surface-state-'));
}
describe('readSurface / writeSurface', () => {
test('round-trips a complete surface state', () => {
const dir = tmpDir();
try {
const state = {
baseProfile: 'standard',
disabledClusters: ['utility'],
explicitAdds: ['sketch'],
explicitRemoves: [],
};
writeSurface(dir, state);
const read = readSurface(dir);
assert.deepStrictEqual(read, state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips empty arrays', () => {
const dir = tmpDir();
try {
const state = {
baseProfile: 'core',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: [],
};
writeSurface(dir, state);
assert.deepStrictEqual(readSurface(dir), state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('round-trips composed base profile', () => {
const dir = tmpDir();
try {
const state = {
baseProfile: 'core,audit',
disabledClusters: [],
explicitAdds: [],
explicitRemoves: ['health'],
};
writeSurface(dir, state);
assert.deepStrictEqual(readSurface(dir), state);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('missing file returns null', () => {
const dir = tmpDir();
try {
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('non-existent directory returns null', () => {
const ghost = path.join(os.tmpdir(), 'gsd-surface-no-exist-' + Date.now());
const result = readSurface(ghost);
assert.strictEqual(result, null);
});
test('corrupt JSON returns null', () => {
const dir = tmpDir();
try {
fs.writeFileSync(path.join(dir, '.gsd-surface.json'), '{not valid json', 'utf8');
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('JSON missing baseProfile field returns null', () => {
const dir = tmpDir();
try {
fs.writeFileSync(
path.join(dir, '.gsd-surface.json'),
JSON.stringify({ disabledClusters: [], explicitAdds: [], explicitRemoves: [] }),
'utf8'
);
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('JSON with non-array disabledClusters returns null', () => {
const dir = tmpDir();
try {
fs.writeFileSync(
path.join(dir, '.gsd-surface.json'),
JSON.stringify({ baseProfile: 'standard', disabledClusters: 'utility', explicitAdds: [], explicitRemoves: [] }),
'utf8'
);
const result = readSurface(dir);
assert.strictEqual(result, null);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('atomic write: result file is never a partial tmp file', () => {
const dir = tmpDir();
try {
const state = { baseProfile: 'full', disabledClusters: [], explicitAdds: [], explicitRemoves: [] };
writeSurface(dir, state);
// No .tmp.* files should remain
const files = fs.readdirSync(dir);
const tmpFiles = files.filter(f => f.includes('.tmp.'));
assert.deepStrictEqual(tmpFiles, [], 'no tmp files should remain after write');
// The canonical file exists
assert.ok(files.includes('.gsd-surface.json'));
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('second write overwrites first', () => {
const dir = tmpDir();
try {
writeSurface(dir, { baseProfile: 'core', disabledClusters: [], explicitAdds: [], explicitRemoves: [] });
writeSurface(dir, { baseProfile: 'standard', disabledClusters: ['utility'], explicitAdds: [], explicitRemoves: [] });
const read = readSurface(dir);
assert.strictEqual(read.baseProfile, 'standard');
assert.deepStrictEqual(read.disabledClusters, ['utility']);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('writeSurface creates directory if it does not exist', () => {
const base = tmpDir();
const nested = path.join(base, 'skills', 'subdir');
try {
writeSurface(nested, { baseProfile: 'full', disabledClusters: [], explicitAdds: [], explicitRemoves: [] });
assert.ok(fs.existsSync(nested));
assert.ok(readSurface(nested) !== null);
} finally {
fs.rmSync(base, { recursive: true, force: true });
}
});
});