# P2: Agent Skills 标准兼容 + References 加载 实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 兼容 Agent Skills 社区标准(名称验证、hidden skill、字段映射),改造 prompt 索引区分三种 skill 类型,改造 read_skill 返回格式强制 Agent 读取 required references。 **Architecture:** 新增 `validateSkillName()` 验证函数。`buildSkillsPrompt()` 输出增加 type 属性和 compute-entry 的 interface 摘要。`read_skill` 工具返回区分 `` 和 ``。基础设施文件从 collectFiles 中过滤。 **Tech Stack:** TypeBox, XML 模板, Midway.js **Spec:** `docs/superpowers/specs/2026-04-27-skill-system-evolution-design.md` Section 5 + 10.5-10.7 + 10.10 **Depends on:** P1 (SkillConfigService, SkillClassification must be integrated into SkillLoaderService before P2 Task 2 can work. Execute P1 Tasks 1-2 first.) --- ### Task 1: 名称验证函数(无依赖,可独立执行) **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts` - [ ] **Step 1: 在 skill_loader.ts 顶部新增 validateSkillName 函数** 在 class 定义之前添加: ```typescript const MAX_NAME_LENGTH = 64; export function validateSkillName(name: string, parentDirName?: string): string[] { const errors: string[] = []; if (!name) { errors.push('name is required'); return errors; } if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters`); if (!/^[a-z0-9-]+$/.test(name)) errors.push('name must be lowercase a-z, 0-9, hyphens only'); if (name.startsWith('-') || name.endsWith('-')) errors.push('name must not start or end with hyphen'); if (name.includes('--')) errors.push('name must not contain consecutive hyphens'); if (parentDirName && name !== parentDirName) errors.push(`name "${name}" does not match directory "${parentDirName}"`); return errors; } ``` - [ ] **Step 2: 在 parseSkillMd 中调用验证(warning 级别,不阻塞加载)** 在 `parseSkillMd` 方法中,解析出 name 后添加: ```typescript const nameErrors = validateSkillName(name); if (nameErrors.length > 0) { this.logger.warn('[SkillLoader] Skill "%s" name validation: %s', name, nameErrors.join(', ')); } ``` - [ ] **Step 3: 在 skill_manage.ts 和 skill_installer.ts 的创建/安装流程中调用验证(error 级别,阻塞)** 在 `skill_manage.ts` 的 `execute` 方法中,`create` 分支的 `parseSkillMd` 之后添加: ```typescript const nameErrors = validateSkillName(parsed.name, params.name); if (nameErrors.length > 0) { return textResult(`名称不合规: ${nameErrors.join('; ')}`); } ``` 在 `skill_installer.ts` 的 `installFromGitHub` 和 `installFromZip` 中,`parseSkillMd` 之后添加类似校验。 - [ ] **Step 4: Commit** ```bash git add packages/backend/src/modules/netaclaw/service/skill_loader.ts \ packages/backend/src/modules/netaclaw/tools/builtin/skill_manage.ts \ packages/backend/src/modules/netaclaw/service/skill_installer.ts git commit -m "feat(skill): add Agent Skills standard name validation" ``` --- ### Task 2: buildSkillsPrompt 输出改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts` - [ ] **Step 1: 改造 buildSkillsPrompt 方法** 替换现有 `buildSkillsPrompt` 方法: ```typescript buildSkillsPrompt(skillNames: string[], availableTools: string[]): string { const filtered = this.filterByConditions(skillNames, availableTools); if (filtered.length === 0) return ''; const lines: string[] = []; let totalChars = 0; for (const s of filtered) { // 跳过 hidden skill const fm = s.metadata as any; if (fm?.['disable-model-invocation'] === true) continue; const classification = this.skillConfig.classify(s.name); const category = fm?.category || '通用'; const tags = Array.isArray(fm?.tags) ? fm.tags.join(',') : ''; let body = ` ${s.description}`; // compute-entry: 附带 interface.input 摘要 if (classification === 'compute-entry') { const config = this.skillConfig.getConfig(s.name); if (config?.interface?.input) { const inputDesc = Object.entries(config.interface.input) .map(([k, v]) => `${k}(${v.type}${v.required ? ',必填' : ''}${v.default ? ',默认' + v.default : ''})`) .join(', '); body += `\n 输入: ${inputDesc}`; } } const line = ` \n${body}\n `; if (totalChars + line.length > SkillLoaderService.MAX_SKILLS_PROMPT_CHARS) break; lines.push(line); totalChars += line.length; } if (lines.length === 0) return ''; return `\n\n## 技能(必须扫描)\n回复前,扫描以下技能列表。prompt 类型用 read_skill 加载指令,compute-entry 类型用 execute_skill 直接调用,compute-toolkit 类型用 read_skill 加载指令后通过 bash 执行。\n读取 skill 后,如果返回中包含 ,你必须在执行任何操作前先用 read_skill_file 逐一读取列出的所有文档。这不是建议,是强制要求。\n\n\n${lines.join('\n')}\n`; } ``` - [ ] **Step 2: 验证编译通过** Run: `cd packages/backend && npx tsc --noEmit` - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/service/skill_loader.ts git commit -m "feat(skill): enhance buildSkillsPrompt with type classification and required references guidance" ``` --- ### Task 3: read_skill 返回格式改造 **Files:** - Modify: `packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts` - [ ] **Step 1: 改造 read_skill 的 execute 方法** 替换现有 `execute` 方法体: ```typescript async execute(_id, params) { const skill = skillLoader.getSkill(params.name); if (!skill) { return textResult(`未找到名为 "${params.name}" 的 skill`); } let result = skill.content; // 获取 references 配置 const config = skillLoader.getSkillConfig(params.name); const configRefs = config?.references; const fmRefs = (skill.metadata as any)?.references; const refs = configRefs || fmRefs || null; const requiredRefs: string[] = refs?.required || []; const allFiles = skill.files || []; // 基础设施文件过滤 const INFRA_FILES = new Set([ 'skill.config.yaml', 'requirements.txt', 'package.json', 'package-lock.json', 'tsconfig.json', '.env', ]); const referenceFiles = allFiles.filter(f => !INFRA_FILES.has(f) && !requiredRefs.includes(f)); if (requiredRefs.length > 0) { result += `\n\n`; result += `\n⚠️ 执行此 skill 的任务前,你必须先用 read_skill_file 读取以下文档:`; for (const ref of requiredRefs) { result += `\n- ${ref}`; } result += `\n未读取这些文档就执行操作会导致错误。`; result += `\n`; } if (referenceFiles.length > 0) { result += `\n\n`; result += `\n以下文档可按需读取(用 read_skill_file 工具):`; for (const ref of referenceFiles) { result += `\n- ${ref}`; } result += `\n`; } return textResult(result); }, ``` - [ ] **Step 2: 验证编译通过** Run: `cd packages/backend && npx tsc --noEmit` - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts git commit -m "feat(skill): enhance read_skill with required/optional references separation" ``` --- ### Task 4: collectFiles 基础设施文件过滤 **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/skill_loader.ts` - [ ] **Step 1: 在 collectFiles 方法中过滤基础设施文件** 在 `collectFiles` 方法的 `else if (entry.name !== 'SKILL.md')` 条件中,增加过滤: ```typescript const INFRA_FILES = new Set([ 'skill.config.yaml', 'requirements.txt', 'package.json', 'package-lock.json', 'tsconfig.json', '.env', ]); // 在 else if 分支中 } else if (entry.name !== 'SKILL.md' && !INFRA_FILES.has(entry.name)) { const rel = path.relative(baseDir, fullPath).replace(/\\/g, '/'); results.push(rel); } ``` - [ ] **Step 2: Commit** ```bash git add packages/backend/src/modules/netaclaw/service/skill_loader.ts git commit -m "feat(skill): filter infrastructure files from skill file listing" ``` --- ### Task 5: read_skill task 参数与 routes 匹配(Spec 10.10) **Files:** - Modify: `packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts` - [ ] **Step 1: 给 ReadSkillParams 新增可选 task 参数** ```typescript const ReadSkillParams = Type.Object({ name: Type.String({ description: '要读取的 skill 名称' }), task: Type.Optional(Type.String({ description: '当前任务描述,用于自动匹配需要读取的文档' })), }); ``` - [ ] **Step 2: 在 execute 方法中实现 routes 匹配** 在获取 `requiredRefs` 之后、构建返回结果之前,添加 routes 匹配逻辑: ```typescript // routes 匹配:如果传入了 task 且 config 有 routes,尝试关键词匹配 let routeMatchedRefs: string[] = []; if (params.task && refs?.routes) { const taskLower = params.task.toLowerCase(); for (const route of refs.routes) { if (route.match.some(keyword => taskLower.includes(keyword.toLowerCase()))) { routeMatchedRefs.push(...route.required_refs); } } routeMatchedRefs = [...new Set(routeMatchedRefs)]; // 去重 } // 合并 required + route matched const allRequired = [...new Set([...requiredRefs, ...routeMatchedRefs])]; ``` 如果 `routeMatchedRefs` 命中了文档,尝试直接读取并拼接到返回结果中(减少 Agent 二次调用): ```typescript if (routeMatchedRefs.length > 0) { result += `\n\n`; for (const ref of routeMatchedRefs) { const filePath = skillLoader.getSkillFilePath(params.name, ref); if (filePath) { try { const content = await fs.readFile(filePath, 'utf-8'); result += `\n\n--- ${ref} ---\n${content}`; } catch { /* skip unreadable */ } } } result += `\n`; } ``` - [ ] **Step 3: Commit** ```bash git add packages/backend/src/modules/netaclaw/tools/builtin/read_skill.ts git commit -m "feat(skill): add task parameter and routes matching to read_skill tool" ``` --- ### Task 6: Agent 分配 skill 时的类型校验(Spec 10.6) **Files:** - Modify: `packages/frontend/src/modules/agent/views/agent-edit.vue` - Modify: `packages/backend/src/modules/netaclaw/controller/agent.ts` - [ ] **Step 1: metas 端点返回 classification 字段** 在 `SkillLoaderService.getSkillMetas()` 中,返回对象添加(如果 P3 Task 5 尚未添加): ```typescript classification: this.skillConfig.classify(fs.name), ``` - [ ] **Step 2: 前端 skill 选择器显示分类标签** 在 `agent-edit.vue` 的可选 Skill 列表项中(约 line 38),在现有 `skillType` 标签旁新增分类标签: ```vue {{ sk.classification }} ``` 已选择列表中同样添加: ```vue {{ getSkillLabel(name) }} {{ getSkillClassification(name) }} ``` 新增辅助函数: ```typescript function getSkillClassification(name: string): string { const meta = skillMetas.value.find((s) => s.name === name); return meta?.classification || 'prompt'; } ``` - [ ] **Step 3: 前端选择 compute skill 时显示工具权限 warning** 在 `addSkill` 函数中添加校验: ```typescript function addSkill(name: string) { if (!form.value.skills.includes(name)) { form.value.skills.push(name); // 校验工具权限 const classification = getSkillClassification(name); if (classification === 'compute-entry') { ElMessage.warning(`"${name}" 是 compute-entry 类型,请确保该 Agent 已启用 execute_skill 工具`); } else if (classification === 'compute-toolkit') { ElMessage.warning(`"${name}" 是 compute-toolkit 类型,请确保该 Agent 已启用 bash 工具`); } } } ``` - [ ] **Step 4: 后端保存时返回 warnings** 在 `controller/agent.ts` 的 `update` 方法中,注入 `SkillLoaderService`,保存前校验: ```typescript @Inject() skillLoader: SkillLoaderService; @Post('/update') async update(@Body() body: any) { const warnings: string[] = []; if (body.skills?.length) { for (const skillName of body.skills) { const classification = this.skillLoader.getSkillClassification(skillName); if (classification === 'compute-entry') { warnings.push(`Skill "${skillName}" 需要 execute_skill 工具`); } else if (classification === 'compute-toolkit') { warnings.push(`Skill "${skillName}" 需要 bash 工具`); } } } await this.agentService.update(body); return { code: 1000, message: 'success', warnings }; } ``` - [ ] **Step 5: Commit** ```bash git add packages/frontend/src/modules/agent/views/agent-edit.vue \ packages/backend/src/modules/netaclaw/controller/agent.ts git commit -m "feat(skill): add skill classification display and tool permission warnings in agent editor" ``` --- ### Task 7: skill_context.ts 废弃标记(Spec 10.12) **Files:** - Modify: `packages/backend/src/modules/netaclaw/service/skill_context.ts` - [ ] **Step 1: 添加 @deprecated 注释** 在 `buildSkillContext` 函数上方添加: ```typescript /** * @deprecated 使用 tool_resolver.ts 中的 skill 工具注入逻辑替代。 * 此函数不再被主链路调用,保留仅为兼容可能的外部引用。 */ ``` - [ ] **Step 2: Commit** ```bash git add packages/backend/src/modules/netaclaw/service/skill_context.ts git commit -m "chore(skill): mark buildSkillContext as deprecated" ```