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
This commit is contained in:
程序员阿江(Relakkes) 2026-07-03 18:43:14 +08:00
parent da37f4b1ed
commit f5f4ff9858
2 changed files with 78 additions and 39 deletions

View File

@ -1,4 +1,7 @@
import { afterEach, describe, expect, it, mock } from 'bun:test' 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 { import {
handleSkillMarketApi, handleSkillMarketApi,
resetSkillMarketServiceFactoryForTests, resetSkillMarketServiceFactoryForTests,
@ -818,44 +821,80 @@ describe('skill market API', () => {
}) })
it('lists through the service with validated query params', async () => { 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 capturedParams: unknown
let hasInstalledProvider = false let installedNamesFromProvider: string[] = []
setSkillMarketServiceFactoryForTests((options) => {
hasInstalledProvider = typeof options.installedSkillNames === 'function' try {
return { const configDir = path.join(tmpDir, '.claude')
list: async (params) => { const userSkillDir = path.join(configDir, 'skills', 'skill-vetter')
capturedParams = params await fs.mkdir(userSkillDir, { recursive: true })
return { await fs.writeFile(
items: [], path.join(userSkillDir, 'SKILL.md'),
nextCursor: null, [
source: 'skillhub', '---',
sourceStatus: 'ok', 'name: Skill Vetter',
} 'description: Reviews skill packages before install.',
}, '---',
listSkills: async (params) => { '',
capturedParams = params 'Review skills before installing them.',
return { ].join('\n'),
items: [], 'utf-8',
nextCursor: null, )
source: 'skillhub', process.env.CLAUDE_CONFIG_DIR = configDir
sourceStatus: 'ok',
} setSkillMarketServiceFactoryForTests((options) => {
}, return {
list: async (params) => {
capturedParams = params
const installedSkillNames = await (
options.installedSkillNames as (() => Set<string> | Promise<Set<string>>) | undefined
)?.()
installedNamesFromProvider = [...(installedSkillNames ?? new Set<string>())]
return {
items: [],
nextCursor: null,
source: 'skillhub',
sourceStatus: 'ok',
}
},
listSkills: async (params) => {
capturedParams = params
const installedSkillNames = await (
options.installedSkillNames as (() => Set<string> | Promise<Set<string>>) | undefined
)?.()
installedNamesFromProvider = [...(installedSkillNames ?? new Set<string>())]
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
} }
}) await fs.rm(tmpDir, { recursive: true, force: true })
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,
})
}) })
}) })

View File

@ -404,8 +404,8 @@ async function collectAllSkills(cwd?: string): Promise<SkillMeta[]> {
} }
export async function collectUserSkillNames(): Promise<Set<string>> { export async function collectUserSkillNames(): Promise<Set<string>> {
const skills = await collectAllSkills(undefined) const skills = await collectSkillsFromRoots([getUserSkillsDir()], 'user')
return new Set(skills.filter((skill) => skill.source === 'user').map((skill) => skill.name)) return new Set(skills.map((skill) => skill.name))
} }
export async function listSkillSlashCommands(cwd?: string): Promise<SkillSlashCommand[]> { export async function listSkillSlashCommands(cwd?: string): Promise<SkillSlashCommand[]> {