* fix(#783): resolve Kilo global skills base to ~/.kilo/skills getGlobalSkillsBase('kilo') returned ~/.config/kilo/skills (the XDG config dir), but Kilo Code discovers global skills from ~/.kilo/skills/ (the .kilo dir in HOME), independent of the kilo.jsonc config dir. Add a HOME-relative special case so the resolver matches Kilo's actual discovery path. The config dir (~/.config/kilo) and the installer's command/ path are correct and unchanged. This corrects the path used by doctor/status and agent-skills-block resolution; the installer writes commands (not skills) for Kilo, so no files were being written to the wrong location. Closes #783 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#783): set changeset pr to 806 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
105
tests/bug-783-kilo-global-skills-base.test.cjs
Normal file
105
tests/bug-783-kilo-global-skills-base.test.cjs
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
// Regression guard for bug #783.
|
||||
//
|
||||
// getGlobalSkillsBase('kilo') was returning ~/.config/kilo/skills (the XDG
|
||||
// config dir) instead of ~/.kilo/skills — where Kilo Code actually discovers
|
||||
// global skills per its docs:
|
||||
// https://kilo.ai/docs/customize/skills
|
||||
// "Global skills are located in the `.kilo` directory within your Home
|
||||
// directory: ~/.kilo/skills/"
|
||||
//
|
||||
// The fix adds a special case in getGlobalSkillsBase() that resolves kilo's
|
||||
// skills dir from HOME (not from the XDG config dir). The config dir at
|
||||
// ~/.config/kilo is still CORRECT for commands (command/) and must stay
|
||||
// unchanged — this test verifies both roles are separate.
|
||||
|
||||
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,
|
||||
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-homes.cjs'));
|
||||
|
||||
// Helper: temporarily override env vars for a test, restoring them afterwards.
|
||||
function withEnv(overrides, fn) {
|
||||
const saved = {};
|
||||
for (const [key, value] of Object.entries(overrides)) {
|
||||
saved[key] = process.env[key];
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
for (const [key] of Object.entries(overrides)) {
|
||||
if (saved[key] === undefined) delete process.env[key];
|
||||
else process.env[key] = saved[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all kilo-relevant env vars so tests are hermetic.
|
||||
const kiloEnvClears = {
|
||||
KILO_CONFIG_DIR: undefined,
|
||||
XDG_CONFIG_HOME: undefined,
|
||||
};
|
||||
|
||||
describe('bug #783: kilo global skills dir is ~/.kilo/skills, not ~/.config/kilo/skills', () => {
|
||||
test('getGlobalSkillsBase("kilo") resolves to ~/.kilo/skills', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalConfigDir("kilo") still resolves to ~/.config/kilo (config dir unchanged)', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalConfigDir('kilo'),
|
||||
path.join(os.homedir(), '.config', 'kilo'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('kilo skills dir and config dir are decoupled (not equal, not nested)', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
const skillsBase = getGlobalSkillsBase('kilo');
|
||||
const configDir = getGlobalConfigDir('kilo');
|
||||
|
||||
assert.notStrictEqual(skillsBase, configDir, 'skills dir must differ from config dir');
|
||||
assert.ok(
|
||||
!skillsBase.startsWith(configDir + path.sep),
|
||||
`skills dir (${skillsBase}) must not be nested under config dir (${configDir})`,
|
||||
);
|
||||
assert.ok(
|
||||
!configDir.startsWith(skillsBase + path.sep),
|
||||
`config dir (${configDir}) must not be nested under skills dir (${skillsBase})`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalSkillsBase("kilo") is NOT affected by KILO_CONFIG_DIR override', () => {
|
||||
// Skills always live in ~/.kilo/skills regardless of XDG/config-dir overrides.
|
||||
withEnv({ KILO_CONFIG_DIR: '/tmp/custom-kilo-config', XDG_CONFIG_HOME: undefined }, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalSkillsBase("kilo") is NOT affected by XDG_CONFIG_HOME override', () => {
|
||||
withEnv({ KILO_CONFIG_DIR: undefined, XDG_CONFIG_HOME: '/tmp/custom-xdg' }, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user