From f5f4ff98581058fe49d364b85bc76c8f0f9083f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Fri, 3 Jul 2026 18:43:14 +0800 Subject: [PATCH] fix: collect only user skills for market state Tested: bun test src/server/__tests__/skill-market.test.ts Tested: bun test src/server/__tests__/skill-market-install.test.ts Tested: git diff --check Confidence: high Scope-risk: narrow --- src/server/__tests__/skill-market.test.ts | 113 +++++++++++++++------- src/server/api/skills.ts | 4 +- 2 files changed, 78 insertions(+), 39 deletions(-) diff --git a/src/server/__tests__/skill-market.test.ts b/src/server/__tests__/skill-market.test.ts index d436e864..8969f201 100644 --- a/src/server/__tests__/skill-market.test.ts +++ b/src/server/__tests__/skill-market.test.ts @@ -1,4 +1,7 @@ import { afterEach, describe, expect, it, mock } from 'bun:test' +import * as fs from 'node:fs/promises' +import * as os from 'node:os' +import * as path from 'node:path' import { handleSkillMarketApi, resetSkillMarketServiceFactoryForTests, @@ -818,44 +821,80 @@ describe('skill market API', () => { }) it('lists through the service with validated query params', async () => { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skill-market-api-')) + const originalConfigDir = process.env.CLAUDE_CONFIG_DIR let capturedParams: unknown - let hasInstalledProvider = false - setSkillMarketServiceFactoryForTests((options) => { - hasInstalledProvider = typeof options.installedSkillNames === 'function' - return { - list: async (params) => { - capturedParams = params - return { - items: [], - nextCursor: null, - source: 'skillhub', - sourceStatus: 'ok', - } - }, - listSkills: async (params) => { - capturedParams = params - return { - items: [], - nextCursor: null, - source: 'skillhub', - sourceStatus: 'ok', - } - }, + let installedNamesFromProvider: string[] = [] + + try { + const configDir = path.join(tmpDir, '.claude') + const userSkillDir = path.join(configDir, 'skills', 'skill-vetter') + await fs.mkdir(userSkillDir, { recursive: true }) + await fs.writeFile( + path.join(userSkillDir, 'SKILL.md'), + [ + '---', + 'name: Skill Vetter', + 'description: Reviews skill packages before install.', + '---', + '', + 'Review skills before installing them.', + ].join('\n'), + 'utf-8', + ) + process.env.CLAUDE_CONFIG_DIR = configDir + + setSkillMarketServiceFactoryForTests((options) => { + return { + list: async (params) => { + capturedParams = params + const installedSkillNames = await ( + options.installedSkillNames as (() => Set | Promise>) | undefined + )?.() + installedNamesFromProvider = [...(installedSkillNames ?? new Set())] + return { + items: [], + nextCursor: null, + source: 'skillhub', + sourceStatus: 'ok', + } + }, + listSkills: async (params) => { + capturedParams = params + const installedSkillNames = await ( + options.installedSkillNames as (() => Set | Promise>) | undefined + )?.() + installedNamesFromProvider = [...(installedSkillNames ?? new Set())] + return { + items: [], + nextCursor: null, + source: 'skillhub', + sourceStatus: 'ok', + } + }, + } + }) + const url = new URL('/api/skill-market?source=skillhub&sort=updated&q=vetter&cursor=abc&limit=12', 'http://localhost:3456') + const req = new Request(url, { method: 'GET' }) + + const res = await handleSkillMarketApi(req, url, ['api', 'skill-market']) + + expect(res.status).toBe(200) + expect(installedNamesFromProvider).toContain('skill-vetter') + expect(capturedParams).toEqual({ + source: 'skillhub', + sort: 'updated', + query: 'vetter', + cursor: 'abc', + limit: 12, + }) + } finally { + if (originalConfigDir === undefined) { + delete process.env.CLAUDE_CONFIG_DIR + } else { + process.env.CLAUDE_CONFIG_DIR = originalConfigDir } - }) - const url = new URL('/api/skill-market?source=skillhub&sort=updated&q=vetter&cursor=abc&limit=12', 'http://localhost:3456') - const req = new Request(url, { method: 'GET' }) - - const res = await handleSkillMarketApi(req, url, ['api', 'skill-market']) - - expect(res.status).toBe(200) - expect(hasInstalledProvider).toBe(true) - expect(capturedParams).toEqual({ - source: 'skillhub', - sort: 'updated', - query: 'vetter', - cursor: 'abc', - limit: 12, - }) + await fs.rm(tmpDir, { recursive: true, force: true }) + } }) }) diff --git a/src/server/api/skills.ts b/src/server/api/skills.ts index 18745db7..827ca78d 100644 --- a/src/server/api/skills.ts +++ b/src/server/api/skills.ts @@ -404,8 +404,8 @@ async function collectAllSkills(cwd?: string): Promise { } export async function collectUserSkillNames(): Promise> { - const skills = await collectAllSkills(undefined) - return new Set(skills.filter((skill) => skill.source === 'user').map((skill) => skill.name)) + const skills = await collectSkillsFromRoots([getUserSkillsDir()], 'user') + return new Set(skills.map((skill) => skill.name)) } export async function listSkillSlashCommands(cwd?: string): Promise {