* fix(#3126): replace hardcoded globalSkillsBase with runtime-aware mapping Root cause: buildAgentSkillsBlock() used path.join(os.homedir(), '.claude', 'skills') for globalSkillsBase regardless of config.runtime. Cursor users (and every non-Claude runtime) saw their global: skill lookups fail with a warning pointing to the wrong directory. Fix: introduces get-shit-done/bin/lib/runtime-homes.cjs — a pure, side- effect-free module covering all 15 GSD runtimes: Runtime Config base Skills path claude ~/.claude ~/.claude/skills/ cursor ~/.cursor ~/.cursor/skills/ gemini ~/.gemini ~/.gemini/skills/ codex ~/.codex ~/.codex/skills/ copilot ~/.copilot ~/.copilot/skills/ antigravity ~/.gemini/antigravity ...antigravity/skills/ windsurf ~/.codeium/windsurf ...windsurf/skills/ augment ~/.augment ~/.augment/skills/ trae ~/.trae ~/.trae/skills/ qwen ~/.qwen ~/.qwen/skills/ hermes ~/.hermes ~/.hermes/skills/gsd/ (nested #2841) codebuddy ~/.codebuddy ~/.codebuddy/skills/ cline ~/.cline null (rules-based, no skills dir) opencode ~/.config/opencode ...opencode/skills/ kilo ~/.config/kilo ...kilo/skills/ Also adds CLAUDE_CONFIG_DIR env var support (was missing). Warning messages now show the actual runtime-specific path. Docs: INVENTORY.md CLI Modules 41→42. Regression test: 30 assertions across all runtimes. Suite: 7008/7008. Closes #3126. * fix(lint+init): allow-test-rule, fix display path duplication (skillName appended twice)
This commit is contained in:
5
.changeset/fix-3126-global-skills-base-runtime.md
Normal file
5
.changeset/fix-3126-global-skills-base-runtime.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3126
|
||||
---
|
||||
**`global:` skill resolution now uses the correct runtime home directory** — `buildAgentSkillsBlock()` hardcoded `globalSkillsBase` to `~/.claude/skills` regardless of the active runtime, causing every `global:` skill lookup to silently fail on non-Claude runtimes (Cursor, Gemini, Codex, Windsurf, etc.). Introduces `get-shit-done/bin/lib/runtime-homes.cjs` — a first-class runtime→directory mapping module covering all 15 supported runtimes with their canonical env-var overrides. Notable specifics: Hermes Agent uses a nested `skills/gsd/<skillName>/` layout (#2841); Cline is rules-based and returns `null` (no skills directory); `CLAUDE_CONFIG_DIR` env var was previously missing for Claude. Warning messages now show the actual runtime-specific path. Closes #3126.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-05-03",
|
||||
"generated": "2026-05-05",
|
||||
"families": {
|
||||
"agents": [
|
||||
"gsd-advisor-researcher",
|
||||
@@ -273,6 +273,7 @@
|
||||
"profile-pipeline.cjs",
|
||||
"roadmap-command-router.cjs",
|
||||
"roadmap.cjs",
|
||||
"runtime-homes.cjs",
|
||||
"schema-detect.cjs",
|
||||
"secrets.cjs",
|
||||
"security.cjs",
|
||||
|
||||
@@ -346,7 +346,7 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (41 shipped)
|
||||
## CLI Modules (42 shipped)
|
||||
|
||||
Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
|
||||
@@ -382,6 +382,7 @@ Full listing: `get-shit-done/bin/lib/*.cjs`.
|
||||
| `profile-pipeline.cjs` | User behavioral profiling data pipeline, session file scanning |
|
||||
| `roadmap-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools roadmap` |
|
||||
| `roadmap.cjs` | ROADMAP.md parsing, phase extraction, plan progress |
|
||||
| `runtime-homes.cjs` | Canonical runtime → global config/skills directory mapping; first-class support for all 15 runtimes including Hermes nested layout and Cline rules-based exclusion (#3126) |
|
||||
| `schema-detect.cjs` | Schema-drift detection for ORM patterns (Prisma, Drizzle, etc.) |
|
||||
| `secrets.cjs` | Secret-config masking convention (`****<last-4>`) for integration keys managed by `/gsd-config --integrations` — keeps plaintext out of `config-set` output |
|
||||
| `security.cjs` | Path traversal prevention, prompt injection detection, safe JSON/shell helpers |
|
||||
|
||||
@@ -1654,7 +1654,9 @@ function cmdInitRemoveWorkspace(cwd, name, raw) {
|
||||
function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
||||
const { validatePath } = require('./security.cjs');
|
||||
const os = require('os');
|
||||
const globalSkillsBase = path.join(os.homedir(), '.claude', 'skills');
|
||||
const { getGlobalSkillDir, getGlobalSkillDisplayPath } = require('./runtime-homes.cjs');
|
||||
const runtime = (config && config.runtime) || 'claude';
|
||||
const globalSkillsBase = require('./runtime-homes.cjs').getGlobalSkillsBase(runtime);
|
||||
|
||||
if (!config || !config.agent_skills || !agentType) return '';
|
||||
|
||||
@@ -1669,7 +1671,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
||||
for (const skillPath of skillPaths) {
|
||||
if (typeof skillPath !== 'string') continue;
|
||||
|
||||
// Support global: prefix for skills installed at ~/.claude/skills/ (#1992)
|
||||
// Support global: prefix for skills installed at the runtime's global skills directory (#1992, #3126)
|
||||
if (skillPath.startsWith('global:')) {
|
||||
const skillName = skillPath.slice(7);
|
||||
// Explicit empty-name guard before regex for clearer error message
|
||||
@@ -1682,10 +1684,16 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Invalid global skill name "${skillName}" — skipping\n`);
|
||||
continue;
|
||||
}
|
||||
const globalSkillDir = path.join(globalSkillsBase, skillName);
|
||||
// Cline is rules-based and has no global skills directory
|
||||
if (globalSkillsBase === null) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Runtime "${runtime}" does not use a skills directory — "global:${skillName}" is not supported on this runtime\n`);
|
||||
continue;
|
||||
}
|
||||
const globalSkillDir = getGlobalSkillDir(runtime, skillName);
|
||||
const globalSkillMd = path.join(globalSkillDir, 'SKILL.md');
|
||||
const displayPath = getGlobalSkillDisplayPath(runtime, skillName);
|
||||
if (!fs.existsSync(globalSkillMd)) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Global skill not found at "~/.claude/skills/${skillName}/SKILL.md" — skipping\n`);
|
||||
process.stderr.write(`[agent-skills] WARNING: Global skill not found at "${displayPath}/SKILL.md" — skipping\n`);
|
||||
continue;
|
||||
}
|
||||
// Symlink escape guard: validatePath resolves symlinks and enforces
|
||||
@@ -1696,7 +1704,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot) {
|
||||
process.stderr.write(`[agent-skills] WARNING: Global skill "${skillName}" failed path check (symlink escape?) — skipping\n`);
|
||||
continue;
|
||||
}
|
||||
validPaths.push({ ref: `${globalSkillDir}/SKILL.md`, display: `~/.claude/skills/${skillName}` });
|
||||
validPaths.push({ ref: `${globalSkillDir}/SKILL.md`, display: displayPath });
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
178
get-shit-done/bin/lib/runtime-homes.cjs
Normal file
178
get-shit-done/bin/lib/runtime-homes.cjs
Normal file
@@ -0,0 +1,178 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* runtime-homes.cjs — canonical runtime → global config/skills directory mapping.
|
||||
*
|
||||
* Single source of truth for resolving the global config base directory and
|
||||
* the correct global skills directory for every GSD-supported runtime.
|
||||
*
|
||||
* Mirrors the logic in bin/install.js getGlobalDir() but as a pure,
|
||||
* side-effect-free module safe to require() at any point without triggering
|
||||
* the installer. bin/install.js is the authoritative source — keep in sync.
|
||||
*
|
||||
* Runtime-specific notes:
|
||||
* hermes — GSD skills nest under skills/gsd/<skillName>/ (not the flat
|
||||
* skills/<skillName>/ layout used by all other runtimes). This
|
||||
* collapses 86 skill entries into one category in Hermes' system
|
||||
* prompt (#2841).
|
||||
* cline — Rules-based; commands are embedded in .clinerules. Cline does
|
||||
* not use a skills/ directory. getGlobalSkillDir() returns null
|
||||
* for cline so the caller can emit an appropriate warning.
|
||||
*/
|
||||
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Expand a leading ~ to os.homedir().
|
||||
* @param {string} p
|
||||
* @returns {string}
|
||||
*/
|
||||
function expandTilde(p) {
|
||||
if (!p) return p;
|
||||
if (p.startsWith('~/') || p === '~') return path.join(os.homedir(), p.slice(1));
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the global config base directory for the given runtime.
|
||||
* Respects the same env-var overrides as bin/install.js getGlobalDir().
|
||||
*
|
||||
* @param {string} runtime
|
||||
* @returns {string} Absolute path to the runtime's global config directory
|
||||
*/
|
||||
function getGlobalConfigDir(runtime) {
|
||||
const home = os.homedir();
|
||||
const env = process.env;
|
||||
|
||||
switch (runtime) {
|
||||
// ── Claude Code ──────────────────────────────────────────────────────────
|
||||
case 'claude':
|
||||
return env.CLAUDE_CONFIG_DIR ? expandTilde(env.CLAUDE_CONFIG_DIR) : path.join(home, '.claude');
|
||||
|
||||
// ── Cursor ───────────────────────────────────────────────────────────────
|
||||
case 'cursor':
|
||||
return env.CURSOR_CONFIG_DIR ? expandTilde(env.CURSOR_CONFIG_DIR) : path.join(home, '.cursor');
|
||||
|
||||
// ── Gemini CLI ───────────────────────────────────────────────────────────
|
||||
case 'gemini':
|
||||
return env.GEMINI_CONFIG_DIR ? expandTilde(env.GEMINI_CONFIG_DIR) : path.join(home, '.gemini');
|
||||
|
||||
// ── Codex ────────────────────────────────────────────────────────────────
|
||||
case 'codex':
|
||||
return env.CODEX_HOME ? expandTilde(env.CODEX_HOME) : path.join(home, '.codex');
|
||||
|
||||
// ── Copilot (VS Code) ────────────────────────────────────────────────────
|
||||
case 'copilot':
|
||||
return env.COPILOT_CONFIG_DIR ? expandTilde(env.COPILOT_CONFIG_DIR) : path.join(home, '.copilot');
|
||||
|
||||
// ── Antigravity ──────────────────────────────────────────────────────────
|
||||
case 'antigravity':
|
||||
return env.ANTIGRAVITY_CONFIG_DIR
|
||||
? expandTilde(env.ANTIGRAVITY_CONFIG_DIR)
|
||||
: path.join(home, '.gemini', 'antigravity');
|
||||
|
||||
// ── Windsurf ─────────────────────────────────────────────────────────────
|
||||
case 'windsurf':
|
||||
return env.WINDSURF_CONFIG_DIR
|
||||
? expandTilde(env.WINDSURF_CONFIG_DIR)
|
||||
: path.join(home, '.codeium', 'windsurf');
|
||||
|
||||
// ── Augment ──────────────────────────────────────────────────────────────
|
||||
case 'augment':
|
||||
return env.AUGMENT_CONFIG_DIR ? expandTilde(env.AUGMENT_CONFIG_DIR) : path.join(home, '.augment');
|
||||
|
||||
// ── Trae ─────────────────────────────────────────────────────────────────
|
||||
case 'trae':
|
||||
return env.TRAE_CONFIG_DIR ? expandTilde(env.TRAE_CONFIG_DIR) : path.join(home, '.trae');
|
||||
|
||||
// ── Qwen Code ────────────────────────────────────────────────────────────
|
||||
case 'qwen':
|
||||
return env.QWEN_CONFIG_DIR ? expandTilde(env.QWEN_CONFIG_DIR) : path.join(home, '.qwen');
|
||||
|
||||
// ── Hermes Agent ─────────────────────────────────────────────────────────
|
||||
// Note: skills use a nested layout (skills/gsd/<skill>/) — see getGlobalSkillDir().
|
||||
case 'hermes':
|
||||
return env.HERMES_HOME ? expandTilde(env.HERMES_HOME) : path.join(home, '.hermes');
|
||||
|
||||
// ── CodeBuddy ────────────────────────────────────────────────────────────
|
||||
case 'codebuddy':
|
||||
return env.CODEBUDDY_CONFIG_DIR ? expandTilde(env.CODEBUDDY_CONFIG_DIR) : path.join(home, '.codebuddy');
|
||||
|
||||
// ── Cline ────────────────────────────────────────────────────────────────
|
||||
// Note: Cline is rules-based (.clinerules) — no skills/ directory.
|
||||
// getGlobalSkillDir() returns null for cline.
|
||||
case 'cline':
|
||||
return env.CLINE_CONFIG_DIR ? expandTilde(env.CLINE_CONFIG_DIR) : path.join(home, '.cline');
|
||||
|
||||
// ── OpenCode (XDG) ───────────────────────────────────────────────────────
|
||||
case 'opencode': {
|
||||
if (env.OPENCODE_CONFIG_DIR) return expandTilde(env.OPENCODE_CONFIG_DIR);
|
||||
if (env.XDG_CONFIG_HOME) return path.join(expandTilde(env.XDG_CONFIG_HOME), 'opencode');
|
||||
return path.join(home, '.config', 'opencode');
|
||||
}
|
||||
|
||||
// ── Kilo (XDG) ───────────────────────────────────────────────────────────
|
||||
case 'kilo': {
|
||||
if (env.KILO_CONFIG_DIR) return expandTilde(env.KILO_CONFIG_DIR);
|
||||
if (env.XDG_CONFIG_HOME) return path.join(expandTilde(env.XDG_CONFIG_HOME), 'kilo');
|
||||
return path.join(home, '.config', 'kilo');
|
||||
}
|
||||
|
||||
// ── Default (Claude fallback) ─────────────────────────────────────────────
|
||||
default:
|
||||
return env.CLAUDE_CONFIG_DIR ? expandTilde(env.CLAUDE_CONFIG_DIR) : path.join(home, '.claude');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the global skills base directory for the given runtime.
|
||||
* Most runtimes: <configDir>/skills
|
||||
* Hermes: <configDir>/skills/gsd (nested category layout — #2841)
|
||||
* Cline: null (rules-based, no skills directory)
|
||||
*
|
||||
* @param {string} runtime
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function getGlobalSkillsBase(runtime) {
|
||||
if (runtime === 'cline') return null;
|
||||
const configDir = getGlobalConfigDir(runtime);
|
||||
if (runtime === 'hermes') return path.join(configDir, 'skills', 'gsd');
|
||||
return path.join(configDir, 'skills');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full path to a specific skill's directory for the given runtime.
|
||||
* Returns null for runtimes that don't use a skills directory (cline).
|
||||
*
|
||||
* @param {string} runtime
|
||||
* @param {string} skillName - e.g. 'gsd-executor'
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function getGlobalSkillDir(runtime, skillName) {
|
||||
const base = getGlobalSkillsBase(runtime);
|
||||
if (base === null) return null;
|
||||
return path.join(base, skillName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a human-readable display path for a global skill (for log messages).
|
||||
*
|
||||
* @param {string} runtime
|
||||
* @param {string} skillName
|
||||
* @returns {string}
|
||||
*/
|
||||
function getGlobalSkillDisplayPath(runtime, skillName) {
|
||||
const dir = getGlobalSkillDir(runtime, skillName);
|
||||
if (!dir) return `(${runtime} does not use a skills directory)`;
|
||||
// Replace homedir prefix with ~ for readability
|
||||
const home = os.homedir();
|
||||
return dir.startsWith(home) ? '~' + dir.slice(home.length) : dir;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGlobalConfigDir,
|
||||
getGlobalSkillsBase,
|
||||
getGlobalSkillDir,
|
||||
getGlobalSkillDisplayPath,
|
||||
};
|
||||
200
tests/bug-3126-global-skills-base-runtime-path.test.cjs
Normal file
200
tests/bug-3126-global-skills-base-runtime-path.test.cjs
Normal file
@@ -0,0 +1,200 @@
|
||||
'use strict';
|
||||
// allow-test-rule: last three tests read init.cjs source to verify delegation contract to runtime-homes.cjs — structural guard, no behavioral IR exposed
|
||||
|
||||
// Regression guard for bug #3126.
|
||||
//
|
||||
// buildAgentSkillsBlock() in init.cjs hardcoded `globalSkillsBase` to
|
||||
// `~/.claude/skills` regardless of the active runtime. On a Cursor install,
|
||||
// global: skills live under `~/.cursor/skills`, causing every global: lookup
|
||||
// to silently fail with:
|
||||
// [agent-skills] WARNING: Global skill not found at "~/.cursor/skills/X/SKILL.md" — skipping
|
||||
//
|
||||
// Fix introduces get-shit-done/bin/lib/runtime-homes.cjs with first-class
|
||||
// support for all 15 supported runtimes, including:
|
||||
// - hermes: nested skills/gsd/<skillName>/ layout (#2841)
|
||||
// - cline: rules-based, returns null (no skills directory)
|
||||
// - CLAUDE_CONFIG_DIR env var for Claude (was missing)
|
||||
// - All other runtime-specific env vars
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const {
|
||||
getGlobalConfigDir,
|
||||
getGlobalSkillsBase,
|
||||
getGlobalSkillDir,
|
||||
getGlobalSkillDisplayPath,
|
||||
} = require(path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'runtime-homes.cjs'));
|
||||
|
||||
// Helper: run fn with an env var temporarily set
|
||||
function withEnv(key, value, fn) {
|
||||
const orig = process.env[key];
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
try { return fn(); }
|
||||
finally {
|
||||
if (orig === undefined) delete process.env[key];
|
||||
else process.env[key] = orig;
|
||||
}
|
||||
}
|
||||
|
||||
describe('bug #3126: runtime-homes getGlobalConfigDir — defaults', () => {
|
||||
const defaults = [
|
||||
['claude', path.join(os.homedir(), '.claude')],
|
||||
['cursor', path.join(os.homedir(), '.cursor')],
|
||||
['gemini', path.join(os.homedir(), '.gemini')],
|
||||
['codex', path.join(os.homedir(), '.codex')],
|
||||
['copilot', path.join(os.homedir(), '.copilot')],
|
||||
['antigravity', path.join(os.homedir(), '.gemini', 'antigravity')],
|
||||
['windsurf', path.join(os.homedir(), '.codeium', 'windsurf')],
|
||||
['augment', path.join(os.homedir(), '.augment')],
|
||||
['trae', path.join(os.homedir(), '.trae')],
|
||||
['qwen', path.join(os.homedir(), '.qwen')],
|
||||
['hermes', path.join(os.homedir(), '.hermes')],
|
||||
['codebuddy', path.join(os.homedir(), '.codebuddy')],
|
||||
['cline', path.join(os.homedir(), '.cline')],
|
||||
['opencode', path.join(os.homedir(), '.config', 'opencode')],
|
||||
['kilo', path.join(os.homedir(), '.config', 'kilo')],
|
||||
];
|
||||
for (const [runtime, expected] of defaults) {
|
||||
test(`${runtime} default configDir`, () => {
|
||||
// Clear all env vars for this runtime
|
||||
const envKeys = ['CLAUDE_CONFIG_DIR','CURSOR_CONFIG_DIR','GEMINI_CONFIG_DIR',
|
||||
'CODEX_HOME','COPILOT_CONFIG_DIR','ANTIGRAVITY_CONFIG_DIR','WINDSURF_CONFIG_DIR',
|
||||
'AUGMENT_CONFIG_DIR','TRAE_CONFIG_DIR','QWEN_CONFIG_DIR','HERMES_HOME',
|
||||
'CODEBUDDY_CONFIG_DIR','CLINE_CONFIG_DIR','OPENCODE_CONFIG_DIR','KILO_CONFIG_DIR',
|
||||
'XDG_CONFIG_HOME'];
|
||||
const saved = {};
|
||||
for (const k of envKeys) { saved[k] = process.env[k]; delete process.env[k]; }
|
||||
try {
|
||||
assert.strictEqual(getGlobalConfigDir(runtime), expected);
|
||||
} finally {
|
||||
for (const k of envKeys) {
|
||||
if (saved[k] !== undefined) process.env[k] = saved[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
test('unknown runtime falls back to ~/.claude', () => {
|
||||
withEnv('CLAUDE_CONFIG_DIR', undefined, () => {
|
||||
assert.strictEqual(getGlobalConfigDir('unknown-xyz'), path.join(os.homedir(), '.claude'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3126: runtime-homes env-var overrides', () => {
|
||||
test('claude respects CLAUDE_CONFIG_DIR (was missing in old code)', () => {
|
||||
withEnv('CLAUDE_CONFIG_DIR', '/custom/claude', () => {
|
||||
assert.strictEqual(getGlobalConfigDir('claude'), '/custom/claude');
|
||||
});
|
||||
});
|
||||
test('cursor respects CURSOR_CONFIG_DIR', () => {
|
||||
withEnv('CURSOR_CONFIG_DIR', '/custom/cursor', () => {
|
||||
assert.strictEqual(getGlobalConfigDir('cursor'), '/custom/cursor');
|
||||
});
|
||||
});
|
||||
test('opencode respects OPENCODE_CONFIG_DIR', () => {
|
||||
withEnv('OPENCODE_CONFIG_DIR', '/custom/opencode', () => {
|
||||
withEnv('XDG_CONFIG_HOME', undefined, () => {
|
||||
assert.strictEqual(getGlobalConfigDir('opencode'), '/custom/opencode');
|
||||
});
|
||||
});
|
||||
});
|
||||
test('opencode uses XDG_CONFIG_HOME when OPENCODE_CONFIG_DIR absent', () => {
|
||||
withEnv('OPENCODE_CONFIG_DIR', undefined, () => {
|
||||
withEnv('XDG_CONFIG_HOME', '/xdg', () => {
|
||||
assert.strictEqual(getGlobalConfigDir('opencode'), '/xdg/opencode');
|
||||
});
|
||||
});
|
||||
});
|
||||
test('kilo uses XDG_CONFIG_HOME when KILO_CONFIG_DIR absent', () => {
|
||||
withEnv('KILO_CONFIG_DIR', undefined, () => {
|
||||
withEnv('XDG_CONFIG_HOME', '/xdg', () => {
|
||||
assert.strictEqual(getGlobalConfigDir('kilo'), '/xdg/kilo');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3126: runtime-homes getGlobalSkillsBase', () => {
|
||||
test('most runtimes: skills at <configDir>/skills', () => {
|
||||
withEnv('CURSOR_CONFIG_DIR', undefined, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('cursor'),
|
||||
path.join(os.homedir(), '.cursor', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
test('hermes: skills at <configDir>/skills/gsd (nested layout #2841)', () => {
|
||||
withEnv('HERMES_HOME', undefined, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('hermes'),
|
||||
path.join(os.homedir(), '.hermes', 'skills', 'gsd'),
|
||||
);
|
||||
});
|
||||
});
|
||||
test('cline: returns null (rules-based, no skills directory)', () => {
|
||||
assert.strictEqual(getGlobalSkillsBase('cline'), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3126: runtime-homes getGlobalSkillDir', () => {
|
||||
test('cursor: <configDir>/skills/<skillName>', () => {
|
||||
withEnv('CURSOR_CONFIG_DIR', undefined, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillDir('cursor', 'gsd-executor'),
|
||||
path.join(os.homedir(), '.cursor', 'skills', 'gsd-executor'),
|
||||
);
|
||||
});
|
||||
});
|
||||
test('hermes: <configDir>/skills/gsd/<skillName>', () => {
|
||||
withEnv('HERMES_HOME', undefined, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillDir('hermes', 'gsd-executor'),
|
||||
path.join(os.homedir(), '.hermes', 'skills', 'gsd', 'gsd-executor'),
|
||||
);
|
||||
});
|
||||
});
|
||||
test('cline: returns null', () => {
|
||||
assert.strictEqual(getGlobalSkillDir('cline', 'gsd-executor'), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #3126: init.cjs uses runtime-homes not hardcoded .claude', () => {
|
||||
test('init.cjs has no hardcoded globalSkillsBase assignment to ~/.claude/skills', () => {
|
||||
const fs = require('node:fs');
|
||||
const src = fs.readFileSync(
|
||||
path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'init.cjs'),
|
||||
'utf8',
|
||||
);
|
||||
assert.ok(
|
||||
!src.includes("const globalSkillsBase = path.join(os.homedir(), '.claude', 'skills')"),
|
||||
'init.cjs still assigns globalSkillsBase to hardcoded ~/.claude/skills — fix not applied',
|
||||
);
|
||||
});
|
||||
test('init.cjs requires runtime-homes', () => {
|
||||
const fs = require('node:fs');
|
||||
const src = fs.readFileSync(
|
||||
path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'init.cjs'),
|
||||
'utf8',
|
||||
);
|
||||
assert.ok(
|
||||
src.includes('runtime-homes'),
|
||||
'init.cjs does not require runtime-homes.cjs',
|
||||
);
|
||||
});
|
||||
test('init.cjs warning message no longer hardcodes ~/.claude/skills', () => {
|
||||
const fs = require('node:fs');
|
||||
const src = fs.readFileSync(
|
||||
path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'init.cjs'),
|
||||
'utf8',
|
||||
);
|
||||
assert.ok(
|
||||
!src.includes("~/.claude/skills/${skillName}/SKILL.md"),
|
||||
'init.cjs warning message still hardcodes ~/.claude/skills path',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user