diff --git a/src/commands/update.ts b/src/commands/update.ts index 90664a7..e2b03de 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -13,7 +13,7 @@ import { readSkillMetadata, writeSkillMetadata } from '../utils/skill-metadata.j */ export async function updateSkills(skillNames: string[] | string | undefined): Promise { const requested = normalizeSkillNames(skillNames); - const skills = findAllSkills(); + const skills = findAllSkills({ dedupe: false }); if (skills.length === 0) { console.log('No skills installed.\n'); diff --git a/src/utils/skills.ts b/src/utils/skills.ts index d3c8253..81563c8 100644 --- a/src/utils/skills.ts +++ b/src/utils/skills.ts @@ -27,9 +27,10 @@ function isDirectoryOrSymlinkToDirectory(entry: Dirent, parentDir: string): bool /** * Find all installed skills across directories */ -export function findAllSkills(): Skill[] { +export function findAllSkills(options: { dedupe?: boolean } = {}): Skill[] { const skills: Skill[] = []; const seen = new Set(); + const dedupe = options.dedupe ?? true; const dirs = getSearchDirs(); for (const dir of dirs) { @@ -40,7 +41,7 @@ export function findAllSkills(): Skill[] { for (const entry of entries) { if (isDirectoryOrSymlinkToDirectory(entry, dir)) { // Deduplicate: only add if we haven't seen this skill name yet - if (seen.has(entry.name)) continue; + if (dedupe && seen.has(entry.name)) continue; const skillPath = join(dir, entry.name, 'SKILL.md'); if (existsSync(skillPath)) { diff --git a/tests/commands/update.test.ts b/tests/commands/update.test.ts index 41f7568..ac4e95d 100644 --- a/tests/commands/update.test.ts +++ b/tests/commands/update.test.ts @@ -72,4 +72,45 @@ describe('updateSkills', () => { const content = readFileSync(join(targetDir, 'SKILL.md'), 'utf-8'); expect(content).toContain('v1'); }); + + it('updates all installed instances of the same skill name', async () => { + const sourceDir = join(tempRoot, 'source-multi'); + mkdirSync(sourceDir, { recursive: true }); + writeFileSync( + join(sourceDir, 'SKILL.md'), + "---\nname: demo\ndescription: v2\n---\n\n# Demo\nv2\n" + ); + + const projectTarget = join(projectDir, '.claude/skills/demo'); + const globalTarget = join(process.env.HOME!, '.agent/skills/demo'); + mkdirSync(projectTarget, { recursive: true }); + mkdirSync(globalTarget, { recursive: true }); + + writeFileSync( + join(projectTarget, 'SKILL.md'), + "---\nname: demo\ndescription: v1\n---\n\n# Demo\nproject-v1\n" + ); + writeFileSync( + join(globalTarget, 'SKILL.md'), + "---\nname: demo\ndescription: v1\n---\n\n# Demo\nglobal-v1\n" + ); + + writeSkillMetadata(projectTarget, { + source: './source-multi', + sourceType: 'local', + localPath: sourceDir, + installedAt: '2026-01-01T00:00:00.000Z', + }); + writeSkillMetadata(globalTarget, { + source: './source-multi', + sourceType: 'local', + localPath: sourceDir, + installedAt: '2026-01-01T00:00:00.000Z', + }); + + await updateSkills(['demo']); + + expect(readFileSync(join(projectTarget, 'SKILL.md'), 'utf-8')).toContain('v2'); + expect(readFileSync(join(globalTarget, 'SKILL.md'), 'utf-8')).toContain('v2'); + }); });