fix: focus built-in skill defaults
The Skills catalog exposed nine product-marketing recipes as independent capabilities and enabled every marketing package for every Runtime by default. It now keeps five top-level Skills, loads the nine recipes on demand through the product-marketing package, and leaves the three marketing Skills disabled until users enable them. Existing saved Skill settings remain authoritative. Release note: 内置 Skills 已从 14 个顶层入口精简为 5 个;去 AI 味和长文工具默认启用,产品营销类默认停用,已保存的用户设置保持不变。
This commit is contained in:
@@ -56,28 +56,55 @@ describe('bundled skills', () => {
|
||||
it('parses every bundled SKILL.md', async () => {
|
||||
const snapshot = await (await createService()).getSnapshot()
|
||||
|
||||
expect(snapshot.skills.length).toBeGreaterThan(0)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'deai-writing',
|
||||
'longdoc-docx',
|
||||
'product-evidence',
|
||||
'product-marketing',
|
||||
'product-presentation'
|
||||
])
|
||||
)
|
||||
expect(snapshot.skills).toHaveLength(5)
|
||||
expect(snapshot.skills.every((skill) => skill.source === 'builtin')).toBe(
|
||||
true
|
||||
)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).toContain(
|
||||
'product-marketing'
|
||||
expect(
|
||||
snapshot.skills
|
||||
.filter((skill) => skill.enabled)
|
||||
.map((skill) => skill.id)
|
||||
).toEqual(
|
||||
expect.arrayContaining(['deai-writing', 'longdoc-docx'])
|
||||
)
|
||||
expect(
|
||||
snapshot.skills
|
||||
.filter((skill) => !skill.enabled)
|
||||
.map((skill) => skill.id)
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
'product-evidence',
|
||||
'product-marketing',
|
||||
'product-presentation'
|
||||
])
|
||||
)
|
||||
expect(snapshot.skills.map((skill) => skill.id)).not.toContain(
|
||||
'web-3d-game'
|
||||
)
|
||||
})
|
||||
|
||||
it('injects every enabled bundled skill with its resolved directory', async () => {
|
||||
it('injects enabled tools without loading disabled marketing skills', async () => {
|
||||
const service = await createService()
|
||||
const snapshot = await service.getSnapshot()
|
||||
|
||||
const instructions = await service.getSkillInstructions('continue')
|
||||
|
||||
expect(instructions).not.toContain('因超出注入上限未加载')
|
||||
for (const skill of snapshot.skills) {
|
||||
for (const skill of snapshot.skills.filter((item) => item.enabled)) {
|
||||
expect(instructions).toContain(join(builtinSkillsRoot, skill.id))
|
||||
}
|
||||
for (const skill of snapshot.skills.filter((item) => !item.enabled)) {
|
||||
expect(instructions).not.toContain(join(builtinSkillsRoot, skill.id))
|
||||
}
|
||||
})
|
||||
|
||||
it('exposes bundled Skills as native Harness packages', async () => {
|
||||
@@ -88,8 +115,12 @@ describe('bundled skills', () => {
|
||||
).resolves.toMatchObject({
|
||||
packages: expect.arrayContaining([
|
||||
{
|
||||
id: 'product-marketing',
|
||||
directory: join(builtinSkillsRoot, 'product-marketing')
|
||||
id: 'deai-writing',
|
||||
directory: join(builtinSkillsRoot, 'deai-writing')
|
||||
},
|
||||
{
|
||||
id: 'longdoc-docx',
|
||||
directory: join(builtinSkillsRoot, 'longdoc-docx')
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
@@ -358,6 +358,54 @@ describe('CapabilityService', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('disables product marketing skills by default and preserves explicit enablement', async () => {
|
||||
const { filePath, builtinRoot, importedRoot, service } =
|
||||
await createService()
|
||||
await Promise.all([
|
||||
writeSkill(builtinRoot, 'product-marketing', '产品市场'),
|
||||
writeSkill(builtinRoot, 'product-evidence', '产品证据'),
|
||||
writeSkill(builtinRoot, 'product-presentation', '产品演示'),
|
||||
writeSkill(builtinRoot, 'deai-writing', '中文审校'),
|
||||
writeSkill(builtinRoot, 'longdoc-docx', '长文导出')
|
||||
])
|
||||
|
||||
const snapshot = await service.getSnapshot()
|
||||
expect(snapshot.skills).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'product-marketing',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'product-evidence',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'product-presentation',
|
||||
enabled: false
|
||||
}),
|
||||
expect.objectContaining({ id: 'deai-writing', enabled: true }),
|
||||
expect.objectContaining({ id: 'longdoc-docx', enabled: true })
|
||||
])
|
||||
)
|
||||
|
||||
await service.setSkillEnabled('product-marketing', true)
|
||||
const reloaded = new CapabilityService(
|
||||
filePath,
|
||||
builtinRoot,
|
||||
importedRoot,
|
||||
cipher
|
||||
)
|
||||
await expect(reloaded.getSnapshot()).resolves.toMatchObject({
|
||||
skills: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'product-marketing',
|
||||
enabled: true
|
||||
})
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('imports and removes a managed SKILL.md package', async () => {
|
||||
const { directory, service } = await createService()
|
||||
const packageRoot = join(directory, 'source-skill')
|
||||
|
||||
@@ -78,6 +78,11 @@ const MAX_SKILL_PACKAGE_BYTES = 10 * 1024 * 1024
|
||||
const MAX_SKILL_PACKAGE_FILES = 128
|
||||
const MAX_SKILL_DEPTH = 6
|
||||
const MAX_SKILL_DISCOVERY_DEPTH = 4
|
||||
const DEFAULT_DISABLED_BUILTIN_SKILL_IDS = new Set([
|
||||
'product-marketing',
|
||||
'product-evidence',
|
||||
'product-presentation'
|
||||
])
|
||||
const MAX_SKILL_DISCOVERY_RESULTS = 64
|
||||
const MAX_SKILL_INSTRUCTION_CHARACTERS = 262_144
|
||||
const SKILL_DISCOVERY_IGNORED_DIRECTORIES = new Set([
|
||||
@@ -304,9 +309,13 @@ function emptyStoredCapabilities(
|
||||
}
|
||||
}
|
||||
|
||||
function defaultSkillState(): z.infer<typeof skillStateSchema> {
|
||||
function defaultSkillState(
|
||||
skill?: Pick<SkillSummary, 'id' | 'source'>
|
||||
): z.infer<typeof skillStateSchema> {
|
||||
return {
|
||||
enabled: true,
|
||||
enabled:
|
||||
skill?.source !== 'builtin' ||
|
||||
!DEFAULT_DISABLED_BUILTIN_SKILL_IDS.has(skill.id),
|
||||
assignments: [
|
||||
'model',
|
||||
'opencode',
|
||||
@@ -932,7 +941,7 @@ export class CapabilityService {
|
||||
skills: catalog
|
||||
.map((skill) => ({
|
||||
...skill,
|
||||
...(state.skills[skill.id] ?? defaultSkillState())
|
||||
...(state.skills[skill.id] ?? defaultSkillState(skill))
|
||||
}))
|
||||
.sort((left, right) =>
|
||||
left.source === right.source
|
||||
@@ -1664,7 +1673,8 @@ export class CapabilityService {
|
||||
return this.queue(async () => {
|
||||
const id = skillIdSchema.parse(skillId)
|
||||
const catalog = await this.getSkillCatalog()
|
||||
if (!catalog.some((skill) => skill.id === id)) {
|
||||
const skill = catalog.find((item) => item.id === id)
|
||||
if (!skill) {
|
||||
throw new Error('Skill 不存在')
|
||||
}
|
||||
const state = await this.load()
|
||||
@@ -1673,7 +1683,7 @@ export class CapabilityService {
|
||||
skills: {
|
||||
...state.skills,
|
||||
[id]: {
|
||||
...(state.skills[id] ?? defaultSkillState()),
|
||||
...(state.skills[id] ?? defaultSkillState(skill)),
|
||||
...update
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user