fix(#783): resolve Kilo global skills base to ~/.kilo/skills (#806)

* 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:
Tom Boucher
2026-06-07 14:59:33 -04:00
committed by GitHub
parent 5e852611e3
commit c480e04188
3 changed files with 122 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
type: Fixed
pr: 806
---
**`getGlobalSkillsBase('kilo')` now resolves to `~/.kilo/skills`** — where Kilo Code actually discovers global skills — instead of `~/.config/kilo/skills`. Per [Kilo Code docs](https://kilo.ai/docs/customize/skills), global skills live in the `.kilo` directory within HOME (`~/.kilo/skills/`), independent of the XDG-based config dir at `~/.config/kilo`. The kilo.jsonc config dir (`~/.config/kilo`) and the `command/` path used by the installer are correct and unchanged. Blast radius: this corrects the resolved skills-base path used by doctor/status checks and agent-skills-block resolution (`init.cjs`); the installer writes commands (not skills) for Kilo, so no files were previously being written to the wrong location.
<!-- docs-exempt: internal path-resolution correction; no user-facing how-to surface changed -->

View File

@@ -162,8 +162,17 @@ export function getGlobalConfigDir(runtime: string, explicitDir?: string | null)
*/ */
export function getGlobalSkillsBase(runtime: string): string | null { export function getGlobalSkillsBase(runtime: string): string | null {
if (runtime === 'cline') return null; if (runtime === 'cline') return null;
if (runtime === 'hermes') {
const configDir = getGlobalConfigDir(runtime);
return path.join(configDir, 'skills', 'gsd');
}
// Kilo Code discovers global skills from ~/.kilo/skills/ (HOME-relative),
// independent of the XDG-based config dir (~/.config/kilo) used for commands.
// See: https://kilo.ai/docs/customize/skills
// "Global skills are located in the `.kilo` directory within your Home
// directory: ~/.kilo/skills/"
if (runtime === 'kilo') return path.join(os.homedir(), '.kilo', 'skills');
const configDir = getGlobalConfigDir(runtime); const configDir = getGlobalConfigDir(runtime);
if (runtime === 'hermes') return path.join(configDir, 'skills', 'gsd');
return path.join(configDir, 'skills'); return path.join(configDir, 'skills');
} }

View 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'),
);
});
});
});