reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,387 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
// DSH bundle 的守卫测试:package.json 的 `dsh.bundle`、补丁里唯一一行 preset 声明、子插件
// 包名白名单、不继承默认技能根的 skill-filesystem 配置与 REV_SKILLS_DIR 覆盖逻辑、文件卫生。
// 不断言技能计数:预设必须与技能增删解耦(技能数由 tests/counts.test.mjs 看住)。
const DSH_DIR = fileURLToPath(new URL('../', import.meta.url));
const PACKAGE_FILE = join(DSH_DIR, 'package.json');
const PATCH_FILE = join(DSH_DIR, 'dsh.patch.yml');
const PATCH_TEXT = readPatchText();
// 顶层是 `- insert:` 一行,其值才是插入的行列表
const TOP = parseYaml(PATCH_TEXT);
const ROWS = nodeSeq(nodeMap(TOP.value[0]).get('insert'));
assert.equal(ROWS.length, 1, '补丁顶层应是一条 insert 行');
const PRESET_ROW = ROWS[0];
const PRESET_CONFIG = nodeMap(PRESET_ROW.value.get('config'));
// 子插件清单;分组(cordis:group)的 config 本身又是一层子清单
const PLUGINS = nodeSeq(PRESET_CONFIG.get('plugins'));
// 补丁允许出现的全部子插件包名(含 cordis 内置的分组标记)。写死在此处,包名拼错即红。
const ALLOWED_PACKAGES = new Set([
'cordis:group',
'@deepseek-ai/dsh-persona',
'@deepseek-ai/dsh-agent-instructions',
'@deepseek-ai/dsh-tool-bash',
'@deepseek-ai/dsh-tool-pwsh',
'@deepseek-ai/dsh-tool-fs',
'@deepseek-ai/dsh-tool-fs-search',
'@deepseek-ai/dsh-tool-jobs',
'@deepseek-ai/dsh-command-goal',
'@deepseek-ai/dsh-tool-goal',
'@deepseek-ai/dsh-compaction-basic',
'@deepseek-ai/dsh-command-compact',
'@deepseek-ai/dsh-compaction-tool-result-pruner',
'@deepseek-ai/dsh-tool-subagent-control',
'@deepseek-ai/dsh-tool-subagent-control/list-agents',
'@deepseek-ai/dsh-tool-subagent',
'@deepseek-ai/dsh-workflow-ptc',
'@deepseek-ai/dsh-tool-workflow',
'@deepseek-ai/dsh-tool-ask-user',
'@deepseek-ai/dsh-tool-todo',
'@deepseek-ai/dsh-tool-web',
'@deepseek-ai/dsh-skill-filesystem',
'@deepseek-ai/dsh-tool-skill',
'@deepseek-ai/dsh-tool-present',
'@deepseek-ai/dsh-plugin-manager/tools',
]);
// 官方已发布的 preset id:本 bundle 不得与之冲突(Duplicate preset IDs fail declaration loading)。
const SHIPPED_PRESET_IDS = ['standard', 'minimal', 'ptc', 'cordis'];
function readPatchText() {
// 统一成 LF:文件本身必须已是 LF(有单独用例断言)
return readFileSync(PATCH_FILE, 'utf8').replace(/\r\n/g, '\n');
}
// 覆盖本补丁用到的 YAML 子集:映射、序列、块标量、`!!js` 标量。缩进为空格。
// 一切值都包成带 type 的节点,map/seq 的类型不会在传递中丢失。
function parseYaml(text) {
const lines = text.split('\n').filter((l) => l.trim() !== '' && !l.trimStart().startsWith('#'));
let i = 0;
const indentOf = (l) => l.length - l.trimStart().length;
const unquote = (s) => {
if (s.startsWith('"')) return JSON.parse(s);
if (s.startsWith("'")) return s.slice(1, -1);
return s;
};
const scalar = (raw) => {
const s = raw.trim();
const js = /^!!js\s+([\s\S]+)$/.exec(s);
if (js) return { type: 'scalar', value: { js: unquote(js[1].trim()) } };
if (s.startsWith('"') || s.startsWith("'")) return { type: 'scalar', value: unquote(s) };
if (s === 'true') return { type: 'scalar', value: true };
if (s === 'false') return { type: 'scalar', value: false };
if (/^-?\d+$/.test(s)) return { type: 'scalar', value: Number(s) };
return { type: 'scalar', value: s };
};
const parseBlock = () => {
const ind = indentOf(lines[i]);
if (lines[i].slice(ind).startsWith('- ')) return parseSeq(ind);
return parseMap(ind);
};
// `key: value`:值是同行标量、块标量(`>` / `|`)、或更深缩进的子块(映射 / 序列)
const parseEntry = (key, raw, ind, map) => {
if (/^[>|][+-]?$/.test(raw)) {
const block = [];
while (i < lines.length && indentOf(lines[i]) > ind) {
block.push(lines[i].trim());
i++;
}
map.set(key, { type: 'scalar', value: raw.startsWith('>') ? block.join(' ') : block.join('\n') });
return;
}
if (raw !== '') {
map.set(key, scalar(raw));
return;
}
map.set(key, i < lines.length && indentOf(lines[i]) > ind ? parseBlock() : scalar(''));
};
// 序列项为 `- key: value`(可带同层后续键)或 `- 标量`
const parseSeq = (ind) => {
const items = [];
while (i < lines.length) {
const d = indentOf(lines[i]);
if (d !== ind || !lines[i].slice(d).startsWith('- ')) break;
const rest = lines[i].slice(d + 2);
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(rest);
i++;
if (!m) {
items.push(scalar(rest));
continue;
}
const map = new Map();
parseEntry(m[1], m[2], d, map);
while (i < lines.length && indentOf(lines[i]) > d) {
const cd = indentOf(lines[i]);
const cm = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i].slice(cd));
assert.ok(cm, `补丁行无法解析为键值:${JSON.stringify(lines[i])}`);
i++;
parseEntry(cm[1], cm[2], cd, map);
}
items.push({ type: 'map', value: map });
}
return { type: 'seq', value: items };
};
const parseMap = (ind) => {
const map = new Map();
while (i < lines.length) {
const d = indentOf(lines[i]);
if (d !== ind) break;
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(lines[i].slice(d));
if (!m) break;
i++;
parseEntry(m[1], m[2], ind, map);
}
return { type: 'map', value: map };
};
return parseBlock();
}
function nodeValue(node) {
return node && node.type === 'scalar' ? node.value : undefined;
}
function nodeMap(node) {
return node && node.type === 'map' ? node.value : undefined;
}
function nodeSeq(node) {
return node && node.type === 'seq' ? node.value : undefined;
}
function collectPlugins(nodes) {
const out = [];
const walk = (list) => {
for (const n of list) {
if (!n || n.type !== 'map') continue;
const name = nodeValue(n.value.get('name'));
if (name) out.push({ id: nodeValue(n.value.get('id')), name, configNode: n.value.get('config') });
const children = nodeSeq(n.value.get('config'));
if (children) walk(children);
}
};
walk(nodes);
return out;
}
function allPlugins() {
return collectPlugins(PLUGINS);
}
function findPlugin(name) {
return allPlugins().filter((p) => p.name === name);
}
// skill-filesystem 的 rev-skills 技能根表达式(唯一一项 customSkillDirs)
function skillRootExpr() {
const sf = findPlugin('@deepseek-ai/dsh-skill-filesystem');
assert.equal(sf.length, 1, `应有且只有一个 skill-filesystem 行,实际 ${sf.length}`);
return nodeValue(nodeSeq(nodeMap(sf[0].configNode).get('customSkillDirs'))[0]).js;
}
function mutateEnv(vars, fn) {
const saved = vars.map((k) => [k, process.env[k]]);
for (const [k, v] of vars) {
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
try {
return fn();
} finally {
for (const [k, v] of saved) {
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
}
}
function evalExpr(expr, baseUrl, opts = {}) {
// 未提供的变量一律删除(而不是置空串):置空串会让 DSH_HOME 回落到真实用户目录
const env = { REV_SKILLS_DIR: opts.revSkillsDir, DSH_HOME: opts.dshHome };
return mutateEnv(Object.keys(env).map((k) => [k, env[k]]), () => {
const fn = new Function('baseUrl', 'require', 'ctx', `return (${expr});`);
return fn(baseUrl, createRequire(baseUrl), undefined);
});
}
test('根 package.json 是合法 bundle 清单', () => {
const pkg = JSON.parse(readFileSync(PACKAGE_FILE, 'utf8'));
assert.equal(pkg.name, 'rev-skills');
assert.match(pkg.version, /^\d+\.\d+\.\d+/);
assert.match(pkg.license, /Apache-2\.0/, 'bundle 随根包发布,许可证沿用根包');
assert.equal(pkg.type, 'module');
const rel = pkg.dsh?.bundle?.patch;
assert.equal(typeof rel, 'string', 'dsh.bundle.patch 必须存在');
const abs = join(DSH_DIR, rel);
assert.ok(existsSync(abs), `dsh.bundle.patch 指向不存在的文件:${rel}`);
assert.ok(abs.startsWith(DSH_DIR), 'dsh.bundle.patch 必须落在仓库内');
assert.ok(Array.isArray(pkg.files) && pkg.files.length > 0);
for (const f of pkg.files) {
assert.ok(existsSync(join(DSH_DIR, f)), `files 里列了未随包发布的文件:${f}`);
}
});
test('dsh.bundle.patch 指向的文件必须随包发布(files 里漏列即安装期失败)', () => {
const pkg = JSON.parse(readFileSync(PACKAGE_FILE, 'utf8'));
const rel = pkg.dsh?.bundle?.patch;
assert.equal(typeof rel, 'string', 'dsh.bundle.patch 必须存在');
// 归一成包内相对路径:'./dsh.patch.yml' 与 files 里的 'dsh.patch.yml' 视为同一项
const norm = (p) => p.replace(/^\.\//, '').split('\\').join('/');
const target = norm(rel);
const listed = (pkg.files ?? []).map(norm);
assert.ok(
listed.includes(target),
`dsh.bundle.patch 指向 ${rel},但 package.json 的 files 里没有它;`
+ 'pnpm 的 file: 安装按 files 复制,漏列会让 dsh plugin add 报 '
+ 'failed to read overlay … ENOENT 并整体回滚',
);
// bundle 会被整包装进 profile 的 node_modules,不得带上运行时依赖
assert.equal(pkg.dependencies, undefined, 'bundle 包不得有运行时依赖');
});
test('根 LICENSE 是 Apache-2.0 全文,供随包发布的补丁使用', () => {
const license = readFileSync(join(DSH_DIR, 'LICENSE'), 'utf8');
assert.ok(license.includes('Apache License'), 'LICENSE 应含 Apache License 标题行');
assert.ok(license.includes('Version 2.0, January 2004'), 'LICENSE 应是 Apache-2.0 全文');
assert.ok(license.includes('END OF TERMS AND CONDITIONS'), 'LICENSE 应是全文而非节选');
});
test('补丁里只有一行 preset 声明,身份与官方内置 preset 不冲突', () => {
assert.ok(PRESET_ROW.value.has('id'), 'preset 行应有 Loader 寻址用的 id');
assert.equal(nodeValue(PRESET_ROW.value.get('name')), '@deepseek-ai/dsh-agent-preset');
const cfg = PRESET_CONFIG;
assert.ok(cfg instanceof Map, 'preset 行的 config 应是映射');
const id = nodeValue(cfg.get('id'));
assert.equal(id, 'rev-skills');
assert.ok(!SHIPPED_PRESET_IDS.includes(id), `config.id 与官方内置 preset 冲突:${id}`);
assert.ok(!SHIPPED_PRESET_IDS.includes(nodeValue(PRESET_ROW.value.get('id'))), 'Loader 行 id 不应复用官方值');
const order = nodeValue(cfg.get('order'));
assert.ok(Number.isInteger(order) && order > 0, 'order 应是正整数');
for (const key of ['name', 'description']) {
const v = nodeValue(cfg.get(key));
assert.ok(typeof v === 'string' && v.trim().length > 0, `config.${key} 应非空`);
}
});
test('补丁引用的子插件包名都在白名单内', () => {
const plugins = allPlugins();
assert.ok(plugins.length > 10, `补丁应插入完整插件清单,实际 ${plugins.length} 项`);
const unknown = plugins.filter((p) => !ALLOWED_PACKAGES.has(p.name)).map((p) => `${p.id} -> ${p.name}`);
assert.deepEqual(unknown, [], `出现白名单外的包名(疑似拼写错误):\n${unknown.join('\n')}`);
const ids = plugins.map((p) => p.id);
assert.ok(ids.every((id) => typeof id === 'string' && id.length > 0), '每个子插件行都应有 id');
assert.equal(new Set(ids).size, ids.length, `行 id 重复:${ids.join(', ')}`);
});
test('skill-filesystem 行不继承默认技能根、只声明本库技能根,并支持 REV_SKILLS_DIR 覆盖', () => {
const sf = findPlugin('@deepseek-ai/dsh-skill-filesystem');
const cfg = nodeMap(sf[0].configNode);
assert.ok(cfg instanceof Map, 'skill-filesystem 行应有 config');
assert.equal(
nodeValue(cfg.get('includeDefaultRoots')),
false,
'必须显式关闭默认技能根:该提供方只贡献 customSkillDirs 这一层,不继承项目与用户技能根',
);
const dirs = nodeSeq(cfg.get('customSkillDirs'));
assert.ok(Array.isArray(dirs) && dirs.length === 1, 'customSkillDirs 应只有一项(本库技能根)');
const expr = skillRootExpr();
assert.ok(typeof expr === 'string' && expr.trim().length > 0, '技能根应是 !!js 求值表达式');
assert.ok(expr.includes('REV_SKILLS_DIR'), '表达式应支持 REV_SKILLS_DIR 覆盖');
assert.ok(expr.includes('DSH_HOME'), '表达式应回落到 DSH 用户技能根(DSH_HOME)');
assert.ok(expr.includes('node:os'), 'DSH 用户根应由 node:os 推出(os.homedir)');
assert.ok(expr.includes('rev-skills/package.json'), '表达式应支持从本包(rev-skills 即 bundle)解析');
assert.ok(
/(\.claude['"]?\s*,\s*['"]skills|\.claude\/skills)/.test(expr),
'.claude 与 skills 应拼成同一个技能根',
);
assert.ok(expr.includes('existsSync'), '候选目录应用 fs.existsSync 校验存在性');
assert.ok(expr.includes('readdirSync'), '候选目录应用目录列举校验(至少含一个 re- 条目)');
assert.ok(
['node:path', 'node:fs', 'node:module'].every((m) => expr.includes(m)),
'表达式应通过 process.getBuiltinModule 取 node:path / node:fs / node:module',
);
for (const skill of ['re-analyze', 're-kernel', 're-game']) {
assert.ok(!expr.includes(skill), '技能根表达式不得硬编码技能名');
}
});
test('技能根分辨率按「环境变量 -> DSH 用户根 -> 包解析」优先级回落', () => {
const expr = skillRootExpr();
// fileURLToPath 会保留末尾分隔符(`.../skills/`),resolve 归一化后再比较
const repoSkills = resolve(fileURLToPath(new URL('../.claude/skills/', import.meta.url)));
const repoRoot = resolve(fileURLToPath(new URL('..', import.meta.url)));
// 1) REV_SKILLS_DIR 为仓库根 -> 展开成 <dir>/.claude/skills
const viaEnvRoot = evalExpr(expr, PACKAGE_FILE, { revSkillsDir: repoRoot, dshHome: '' });
assert.equal(viaEnvRoot, repoSkills);
// 2) REV_SKILLS_DIR 直接指向技能根 -> 原样使用
const viaEnvDir = evalExpr(expr, PACKAGE_FILE, { revSkillsDir: repoSkills, dshHome: '' });
assert.equal(viaEnvDir, repoSkills);
// 3) 无环境变量且包未安装 -> 回落到 <DSH_HOME>/skills
const home = join(tmpdir(), 'dsh-preset-test-home');
const homeSkills = join(home, 'skills');
mkdirSync(join(homeSkills, 're-test'), { recursive: true });
writeFileSync(join(homeSkills, 're-test', 'SKILL.md'), 'x');
try {
const viaHome = evalExpr(expr, PACKAGE_FILE, { revSkillsDir: '', dshHome: home });
assert.equal(viaHome, homeSkills);
} finally {
rmSync(home, { recursive: true, force: true });
}
// 4) 包已安装且 DSH 用户根为空 -> 从包的 package.json 反解 skills 目录
const app = mkdtempSync(join(tmpdir(), 'dsh-preset-app-'));
const fakeRepo = join(app, 'node_modules', 'rev-skills');
const fakeKeys = join(fakeRepo, '.claude', 'skills', 're-fake');
mkdirSync(fakeKeys, { recursive: true });
writeFileSync(join(fakeRepo, 'package.json'), '{}');
writeFileSync(join(fakeKeys, 'SKILL.md'), 'x');
try {
const baseUrl = join(fakeRepo, 'package.json');
// DSH_HOME 指向一个不存在的目录,确保回落到包解析候选
const viaPkg = evalExpr(expr, baseUrl, { revSkillsDir: '', dshHome: join(app, 'empty-dsh-home') });
assert.equal(viaPkg, join(fakeRepo, '.claude', 'skills'));
} finally {
rmSync(app, { recursive: true, force: true });
}
});
test('补丁文件卫生:无 tab、逐行 LF、值里无未引号的冒号空格', () => {
const raw = readFileSync(PATCH_FILE, 'utf8');
assert.ok(!raw.includes('\t'), '补丁不得含 tab 字符');
assert.ok(!raw.includes('\r'), '补丁必须是 LF 行尾(不得出现 CR)');
assert.ok(raw.endsWith('\n'), '补丁应以换行结束');
// 未加引号的 `key: value` 行里,值本身不得含 ": "(会被 YAML 误解析为嵌套映射)
const bad = raw
.split('\n')
.filter((l) => l.includes(': '))
.filter((l) => {
const rest = l.slice(l.indexOf(': ') + 2);
return !/^[>|][+-]?$/.test(rest.trim()) && rest.includes(': ');
});
assert.deepEqual(bad, [], `这些行的标量值里含 ": ",需加引号:\n${bad.join('\n')}`);
});
test('补丁与技能清单解耦,且不写文件', () => {
const raw = PATCH_FILE && PATCH_TEXT;
// 技能名硬编码检查:排除包名与 frontmatter 语义下的 re- 前缀出现在 YAML 值里的情况
const names = raw.split('\n').filter((l) => /(^|[^a-zA-Z0-9-])re-[a-z0-9-]+/.test(l) && !l.includes('@deepseek-ai/'));
assert.deepEqual(names, [], `补丁不得硬编码技能名(预设需与技能数解耦):\n${names.join('\n')}`);
for (const re of [/writeFile\w*/, /appendFile\w*/, /\bmkdir\w*/, /\bunlink\w*/, /\brmSync\b/, /\bcreateWriteStream\b/]) {
assert.ok(!re.test(raw), `补丁不得写文件,命中 ${re}`);
}
const repoSkills = fileURLToPath(new URL('../.claude/skills/', import.meta.url));
assert.ok(existsSync(repoSkills), '技能根必须真实存在,否则预设挂载空目录');
const entries = readdirSync(repoSkills).filter((e) => e.startsWith('re-'));
assert.ok(entries.length > 0, '.claude/skills 下应有 re- 技能目录');
assert.ok(entries.includes('re-analyze'), '入口技能 re-analyze 应存在');
});