UNPKG

dsh-qa-skills

Version:

DeepSeek Harness (dsh) plugin: 10 testing skills (requirement analysis, test strategy, case writing/review, E2E/API automation, exploratory, regression, bug analysis) + shared core knowledge base — a full QA pipeline as agent skills.

112 lines (97 loc) 4.54 kB
/** * qa-skills 插件(DeepSeek Harness / dsh)。 * * 把本仓库 skills/ 下的 10 个测试技能 + core/ 共享知识库注册为 dsh 运行时技能: * 模型经 `skill` 工具按需加载正文,相对路径引用(../core/*.md、references/、scripts/) * 经 resourceBase 指向的技能目录解析。仅用 Node 内建模块,无构建步骤、零运行时依赖 * (dsh 把 @deepseek-ai/* 嵌在自身安装内,插件无法直接 import,全部经注入的 ctx 访问)。 * * 参照模式:obra/superpowers 的 dsh 支持实现(PR #2144)。 */ import { readdirSync, readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; /** Cordis 插件名(loader 诊断与补丁层 insert id 使用)。 */ export const name = 'dsh-qa-skills'; /** 依赖的宿主服务:技能注册表就绪后才激活。 */ export const inject = ['skills']; /** 技能源目录:<包根>/skills。 */ const skillsDir = resolve(dirname(fileURLToPath(import.meta.url)), '../../skills'); /** 共享知识库单元:不面向任务触发(其余 skill 以文件路径引用其内容,不经 skill 工具)。 */ const DEPENDENCY_UNIT = 'core'; /** * 拆分 SKILL.md:frontmatter 字段(仅支持单行 key: value)与指令正文。 * @param {string} raw - SKILL.md 原始内容。 * @returns {{name?: string, description?: string, whenToUse?: string, body: string}} */ const parseSkill = (raw) => { const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); if (!match) return { body: raw.trim() }; const fields = {}; for (const line of match[1].split(/\r?\n/)) { const field = line.match(/^([A-Za-z][\w-]*):\s*(.+)$/); if (field) fields[field[1]] = field[2].trim().replace(/^["'](.*)["']$/, '$1'); } return { ...fields, body: match[2].trim() }; }; /** * 读取全部技能单元(目录含 SKILL.md 才算),跳过并告警不可读或畸形单元。 * @param {{warn: (message: string) => void}} logger - 宿主日志器。 * @returns {Array<{name: string, description: string, whenToUse?: string, content: string, source: string, path: string, invocation?: {modelInvocable: boolean, userInvocable: boolean}, resourceBase: {kind: 'directory', path: string}}>} */ const readSkills = (logger) => { let entries; try { entries = readdirSync(skillsDir, { withFileTypes: true }); } catch (error) { logger.warn(`dsh-qa-skills: cannot read ${skillsDir}: ${error.message}`); return []; } const skills = []; for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { if (!entry.isDirectory()) continue; const skillDir = join(skillsDir, entry.name); const skillPath = join(skillDir, 'SKILL.md'); let raw; try { raw = readFileSync(skillPath, 'utf8'); } catch (error) { logger.warn(`dsh-qa-skills: cannot read ${skillPath}: ${error.message}`); continue; } const skill = parseSkill(raw); if (!skill.name || !skill.description || !skill.body) { logger.warn(`dsh-qa-skills: ${skillPath} needs name and description frontmatter and a body`); continue; } skills.push({ name: skill.name, description: skill.description, ...(skill.whenToUse ? { whenToUse: skill.whenToUse } : {}), content: skill.body, source: 'bundled', path: skillPath, // core 是共享知识库依赖单元:双不可调用,仅作为相对路径引用的落点存在 ...(skill.name === DEPENDENCY_UNIT ? { invocation: { modelInvocable: false, userInvocable: false } } : {}), resourceBase: { kind: 'directory', path: skillDir }, }); } return skills; }; /** * 插件入口:注册全部技能单元。每次注册都是随插件自身 fiber 销毁的 Cordis effect。 * @param {import('@deepseek-ai/cordis').Context} ctx - 宿主注入的插件上下文。 */ export function apply(ctx) { const skills = readSkills(ctx.logger); for (const skill of skills) ctx.skills.register(skill); // 安装完整性哨兵:缺 core 意味着引用路径断裂(其余 skill 的 ../core/*.md 会指向不存在目录) if (!skills.some((skill) => skill.name === DEPENDENCY_UNIT)) { ctx.logger.warn( `dsh-qa-skills: dependency unit "core" is missing from ${skillsDir}; relative references from other skills will break`, ); } ctx.logger.info(`dsh-qa-skills: registered ${skills.length} skill unit(s) from ${skillsDir}`); }