From 4abe1f7fb3dae15109d1a9d77d2cbf12f030272e Mon Sep 17 00:00:00 2001 From: Derek Pearson Date: Sun, 22 Mar 2026 14:11:32 -0400 Subject: [PATCH] fix(skills): prioritize ecosystem dir and skip legacy after migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- packages/pi-coding-agent/src/core/package-manager.ts | 10 +++++++++- packages/pi-coding-agent/src/core/skills.ts | 6 +++--- src/resources/extensions/gsd/preferences-skills.ts | 4 ++-- src/resources/extensions/gsd/skill-telemetry.ts | 9 ++++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/pi-coding-agent/src/core/package-manager.ts b/packages/pi-coding-agent/src/core/package-manager.ts index 44209e04f..0e06eaa5f 100644 --- a/packages/pi-coding-agent/src/core/package-manager.ts +++ b/packages/pi-coding-agent/src/core/package-manager.ts @@ -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, diff --git a/packages/pi-coding-agent/src/core/skills.ts b/packages/pi-coding-agent/src/core/skills.ts index 9ab4df3b7..a8ab488ef 100644 --- a/packages/pi-coding-agent/src/core/skills.ts +++ b/packages/pi-coding-agent/src/core/skills.ts @@ -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)); } } diff --git a/src/resources/extensions/gsd/preferences-skills.ts b/src/resources/extensions/gsd/preferences-skills.ts index 59bd4cf69..1ad5a6d39 100644 --- a/src/resources/extensions/gsd/preferences-skills.ts +++ b/src/resources/extensions/gsd/preferences-skills.ts @@ -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; diff --git a/src/resources/extensions/gsd/skill-telemetry.ts b/src/resources/extensions/gsd/skill-telemetry.ts index 33c427c4c..f1bddfd21 100644 --- a/src/resources/extensions/gsd/skill-telemetry.ts +++ b/src/resources/extensions/gsd/skill-telemetry.ts @@ -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) {