fix(skills): prioritize ecosystem dir and skip legacy after migration

Root cause: addAutoDiscoveredResources loaded ~/.gsd/agent/skills/
before ~/.agents/skills/, so the legacy directory always won skill
name collisions. After the one-time migration copied skills to
~/.agents/skills/, both directories had identical skills, producing
collision warnings on every boot.

Two fixes:
1. Swap loading order so ~/.agents/skills/ takes precedence
2. Check .migrated-to-agents marker — when present, skip
   auto-discovery of the legacy dir entirely (no collisions)

Applied consistently across package-manager, skills.ts,
preferences-skills, and skill-telemetry.
This commit is contained in:
Derek Pearson 2026-03-22 14:11:32 -04:00
parent 6cfd1c385c
commit 4abe1f7fb3
4 changed files with 20 additions and 9 deletions

View file

@ -1665,9 +1665,17 @@ export class DefaultPackageManager implements PackageManager {
userOverrides.extensions,
globalBaseDir,
);
// Ecosystem skills (~/.agents/skills/) take priority over legacy config-dir skills.
// Skip legacy dir entirely when migration has completed (marker file present).
const legacySkillsMigrated =
resolve(userDirs.skills) !== resolve(userAgentsSkillsDir) &&
existsSync(join(userDirs.skills, ".migrated-to-agents"));
const legacyUserSkillEntries = legacySkillsMigrated
? []
: collectAutoSkillEntries(userDirs.skills);
addResources(
"skills",
[...collectAutoSkillEntries(userDirs.skills), ...collectAutoSkillEntries(userAgentsSkillsDir)],
[...collectAutoSkillEntries(userAgentsSkillsDir), ...legacyUserSkillEntries],
userMetadata,
userOverrides.skills,
globalBaseDir,

View file

@ -426,9 +426,9 @@ export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
// Legacy fallback: read skills from ~/.gsd/agent/skills/ so existing
// installs keep working until the one-time migration in resource-loader
// copies them to ~/.agents/skills/. Collision dedup above means already-
// migrated skills won't load twice.
if (LEGACY_SKILLS_DIR !== ECOSYSTEM_SKILLS_DIR && existsSync(LEGACY_SKILLS_DIR)) {
// copies them to ~/.agents/skills/. Skip if migration has completed.
const legacyMigrated = existsSync(join(LEGACY_SKILLS_DIR, ".migrated-to-agents"));
if (LEGACY_SKILLS_DIR !== ECOSYSTEM_SKILLS_DIR && existsSync(LEGACY_SKILLS_DIR) && !legacyMigrated) {
addSkills(loadSkillsFromDirInternal(LEGACY_SKILLS_DIR, "user", true));
}
}

View file

@ -32,9 +32,9 @@ export function getSkillSearchDirs(cwd: string): Array<{ dir: string; method: Sk
{ dir: join(homedir(), ".agents", "skills"), method: "user-skill" },
{ dir: join(cwd, ".agents", "skills"), method: "project-skill" },
];
// Legacy fallback — read skills from old GSD directory until migration completes
// Legacy fallback — read skills from old GSD directory only if migration hasn't completed
const legacyDir = join(homedir(), ".gsd", "agent", "skills");
if (existsSync(legacyDir)) {
if (existsSync(legacyDir) && !existsSync(join(legacyDir, ".migrated-to-agents"))) {
dirs.push({ dir: legacyDir, method: "user-skill" });
}
return dirs;

View file

@ -33,8 +33,9 @@ export function captureAvailableSkills(): void {
const skillsDir = join(homedir(), ".agents", "skills");
const legacyDir = join(homedir(), ".gsd", "agent", "skills");
const names = listSkillNames(skillsDir);
// Include skills still in the legacy directory (pre-migration users)
const legacyNames = listSkillNames(legacyDir);
// Include skills still in the legacy directory only if migration hasn't completed
const legacyMigrated = existsSync(join(legacyDir, ".migrated-to-agents"));
const legacyNames = legacyMigrated ? [] : listSkillNames(legacyDir);
const all = new Set([...names, ...legacyNames]);
availableSkills = [...all];
activelyLoadedSkills.clear();
@ -106,7 +107,9 @@ export function detectStaleSkills(
// Check all installed skills, not just those with usage data
const skillsDir = join(homedir(), ".agents", "skills");
const legacyDir = join(homedir(), ".gsd", "agent", "skills");
const installedSet = new Set([...listSkillNames(skillsDir), ...listSkillNames(legacyDir)]);
const legacyMigrated = existsSync(join(legacyDir, ".migrated-to-agents"));
const legacyNames = legacyMigrated ? [] : listSkillNames(legacyDir);
const installedSet = new Set([...listSkillNames(skillsDir), ...legacyNames]);
const installed = [...installedSet];
for (const skill of installed) {