From 33ac9b304a4997a8d406e059d7277785b8922e8d Mon Sep 17 00:00:00 2001 From: zhoutianyi Date: Tue, 15 Sep 2026 17:35:35 +0800 Subject: [PATCH 1/6] feat(v2): add the coding-agent support matrix as shared host data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重建说明:本提交由会话转录重建,提交信息取自原分支(ds/05-agents)。 文件内容为集成分支上的最终态,不是当时那一刻的中间态——原分支的 per-commit 文件树随 /tmp 清空丢失,转录只保留了提交信息与 git add 的路径清单。 原提交信息:feat(v2): add the coding-agent support matrix as shared host data --- src/hosts/agents.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hosts/agents.mjs b/src/hosts/agents.mjs index 26b22fd0..e2cb3637 100644 --- a/src/hosts/agents.mjs +++ b/src/hosts/agents.mjs @@ -104,8 +104,8 @@ export const AGENTS = [ projectPath: '.dsh/skills/distilly', capability: 'full', note: { - zh: 'DSH_HOME 未设置时等价于 ~/.dsh/skills/distilly;社区集成,非官方 DeepSeek 产品。上游 AgentSkills CLI 没有 DSH 目标,所以只提供 clone 路线。', - en: 'Falls back to ~/.dsh/skills/distilly when DSH_HOME is unset; community integration, not an official DeepSeek product. The upstream AgentSkills CLI has no DSH target, so only the clone route is offered.', + zh: 'DSH_HOME 未设置时等价于 ~/.dsh/skills/distilly;社区集成,非官方 DeepSeek 产品。', + en: 'Falls back to ~/.dsh/skills/distilly when DSH_HOME is unset; community integration, not an official DeepSeek product.', }, }, { @@ -176,3 +176,4 @@ export function cloneCommand(id, scope = 'global') { if (!target) throw new Error(`${agent.label} has no documented project-local path; use the global install.`); return `git clone https://github.com/${REPO} ${target}`; } + From a9b515796f789ea8c4f38351c70dff91c67545f3 Mon Sep 17 00:00:00 2001 From: zhoutianyi Date: Tue, 15 Sep 2026 17:35:35 +0800 Subject: [PATCH 2/6] =?UTF-8?q?chore(agents.test):=20=E9=87=8D=E5=BB=BA=20?= =?UTF-8?q?tests/agents.test.mjs=EF=BC=88=E5=8E=9F=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9C=AA=E8=AE=B0=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重建说明:本提交由会话转录重建,提交信息取自原分支(ds/05-agents)。 文件内容为集成分支上的最终态,不是当时那一刻的中间态——原分支的 per-commit 文件树随 /tmp 清空丢失,转录只保留了提交信息与 git add 的路径清单。 原提交信息:(未记录) --- tests/agents.test.mjs | 236 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 tests/agents.test.mjs diff --git a/tests/agents.test.mjs b/tests/agents.test.mjs new file mode 100644 index 00000000..4cd2d992 --- /dev/null +++ b/tests/agents.test.mjs @@ -0,0 +1,236 @@ +/** + * Coding-agent matrix assertions. + * + * Two things must never drift apart: + * 1. `src/hosts/agents.mjs` — the single source of truth for docs and commands + * 2. `bin/distilly.mjs` — the directories the installer actually writes to + * + * The second one is a hand-written table inside a plain script, so this test + * reads that file, evaluates only its `join(...)` expressions, and compares the + * result with the matrix. Run with: `node --test tests/agents.test.mjs`. + */ + +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { HOST_ALIASES, repoInstallDir, supportedHosts } from '../src/install/hosts.mjs'; + +import { + AGENTS, + REPO, + SKILL_NAME, + cloneCommand, + getAgent, + listAgents, + skillsCliCommand, + skillsCliSupported, +} from '../src/hosts/agents.mjs'; + +const repoRoot = fileURLToPath(new URL('..', import.meta.url)); + +/** Hosts that intentionally have no documented project-local directory. */ +const GLOBAL_ONLY = ['openclaw']; + +/** + * Hosts that must have a confirmed AgentSkills CLI `--agent` target, and the + * one host that must not: the upstream registry (verify with + * `npx skills ls -a `) has no DeepSeek Harness entry. + */ +const CLI_SUPPORTED = [ + 'claude-code', + 'codex', + 'opencode', + 'openclaw', + 'hermes', + 'grok-build', + 'pi', +]; +const CLONE_ONLY = ['deepseek-harness']; + +/** + * `$DSH_HOME/skills/distilly` and `~/.dsh/skills/distilly` are the same target + * spelled two ways; collapse both to the `$DSH_HOME` spelling. + */ +function normalizeTarget(target) { + const home = homedir(); + const withTilde = target.startsWith(`${home}/`) ? `~/${target.slice(home.length + 1)}` : target; + return withTilde.replace(/^~\/\.dsh\//, '$DSH_HOME/').replace(/\/{2,}/g, '/'); +} + +test('matrix shape: 8 unique hosts, each with a global path and a bilingual note', () => { + assert.equal(AGENTS.length, 8, 'the matrix must describe exactly 8 hosts'); + + const ids = AGENTS.map((agent) => agent.id); + assert.equal(new Set(ids).size, ids.length, `duplicate host id in ${ids.join(', ')}`); + assert.deepEqual(listAgents(), ids, 'listAgents() must return the matrix ids in order'); + + for (const agent of AGENTS) { + assert.equal(typeof agent.globalPath, 'string', `${agent.id}: globalPath must be a string`); + assert.ok(agent.globalPath.length > 0, `${agent.id}: globalPath must not be empty`); + assert.ok(agent.note && typeof agent.note === 'object', `${agent.id}: note must be an object`); + for (const language of ['zh', 'en']) { + assert.equal( + typeof agent.note[language], + 'string', + `${agent.id}: note.${language} must be a string`, + ); + assert.ok( + agent.note[language].trim().length > 0, + `${agent.id}: note.${language} must not be empty`, + ); + } + assert.ok(['full', 'prompt-only'].includes(agent.capability), `${agent.id}: unknown capability`); + assert.equal(getAgent(agent.id), agent, `getAgent('${agent.id}') must return the matrix entry`); + } +}); + +test('installer and matrix agree host-for-host', () => { + // ds/01 moved the inline `hosts` table out of bin/distilly.mjs into + // src/install/hosts.mjs, so the anti-drift check now goes through that API + // instead of regex-parsing the executable. + assert.deepEqual( + [...supportedHosts()].sort(), + [...listAgents()].sort(), + 'src/install/hosts.mjs and src/hosts/agents.mjs must list the same hosts', + ); + + for (const id of listAgents()) { + // A fixed env, not the ambient one: the assertion is about the *target* the + // installer computes, and this suite may itself be running under a host that + // exports DSH_HOME (the harness does). Without pinning it, `deepseek-harness` + // resolves to that host's `$DSH_HOME/skills/distilly` and the comparison + // against the `$DSH_HOME/...` template fails for an unrelated reason. + assert.equal( + normalizeTarget(repoInstallDir(id, { env: {} })), + normalizeTarget(getAgent(id).globalPath), + `the installer puts ${id} somewhere else than the matrix says`, + ); + } + + // DeepSeek Harness reads $DSH_HOME and falls back to ~/.dsh; both spellings are + // the same target and must stay equivalent to the matrix value. + assert.equal( + normalizeTarget(repoInstallDir('deepseek-harness', { env: {} })), + normalizeTarget(getAgent('deepseek-harness').globalPath), + 'an unset DSH_HOME must resolve to the same global path as $DSH_HOME', + ); + + // `install ` must not be able to reach a host the matrix does not know. + for (const [alias, target] of Object.entries(HOST_ALIASES)) { + assert.ok(listAgents().includes(target), `alias "${alias}" points at unknown host "${target}"`); + } + + // The help text must mention every host, so `distilly install` is discoverable. + const help = execFileSync('node', ['bin/distilly.mjs', '--help'], { encoding: 'utf8' }); + for (const id of listAgents()) { + assert.ok( + new RegExp(`(^|[\\s,])${id}([\\s,]|$)`, 'm').test(help), + `distilly --help never mentions host "${id}"`, + ); + } +}); + +test('only DeepSeek Harness lacks an AgentSkills CLI target, and its error names the clone route', () => { + for (const id of CLONE_ONLY) { + assert.equal( + skillsCliSupported(id), + false, + `${id} has no upstream --agent target and must stay on the clone route`, + ); + + assert.throws( + () => skillsCliCommand(id, 'global', { requireVerified: true }), + (error) => { + assert.ok(error instanceof Error, 'strict mode must throw an Error'); + assert.match( + error.message, + new RegExp(`cloneCommand\\('${id}', 'global'\\)`), + 'the error must name the cloneCommand alternative', + ); + return true; + }, + `${id} must refuse an unverified --agent target`, + ); + } + + assert.equal( + CLI_SUPPORTED.length + CLONE_ONLY.length, + AGENTS.length, + 'every host must be either CLI-capable or clone-only', + ); + + for (const id of CLI_SUPPORTED) { + assert.equal(skillsCliSupported(id), true, `${id} must have a confirmed --agent target`); + assert.match(getAgent(id).cliId, /^[a-z][a-z0-9-]*$/, `${id}: malformed --agent target`); + const command = skillsCliCommand(id, 'global', { requireVerified: true }); + assert.match(command, /^npx -y skills add titanwings\/distilly /, `${id}: unexpected command`); + assert.match(command, new RegExp(`--agent ${getAgent(id).cliId}`), `${id}: wrong --agent flag`); + assert.ok(!command.includes('undefined'), `${id}: command contains "undefined"`); + } +}); + +test('cloneCommand("project") fails for global-only hosts and targets the documented dir', () => { + const withoutProjectPath = AGENTS.filter((agent) => !agent.projectPath).map((agent) => agent.id); + assert.deepEqual( + withoutProjectPath.sort(), + [...GLOBAL_ONLY].sort(), + `only ${GLOBAL_ONLY.join(' and ')} may lack a project-local directory`, + ); + + for (const id of GLOBAL_ONLY) { + assert.throws( + () => cloneCommand(id, 'project'), + (error) => { + assert.match(error.message, /no documented project-local path/); + assert.match(error.message, /global install/); + return true; + }, + `${id} must refuse a project-scope clone`, + ); + } + + for (const agent of AGENTS) { + if (!agent.projectPath) continue; + assert.equal( + cloneCommand(agent.id, 'project'), + `git clone https://github.com/${REPO} ${agent.projectPath}`, + `${agent.id}: project clone must point at ${agent.projectPath}`, + ); + assert.equal( + cloneCommand(agent.id, 'global'), + `git clone https://github.com/${REPO} ${agent.globalPath}`, + `${agent.id}: global clone must point at ${agent.globalPath}`, + ); + } +}); + +test('every emitted command uses the current repo and skill name', () => { + assert.equal(REPO, 'titanwings/distilly'); + assert.equal(SKILL_NAME, 'distilly'); + + const commands = []; + for (const agent of AGENTS) { + commands.push(cloneCommand(agent.id, 'global')); + if (agent.projectPath) commands.push(cloneCommand(agent.id, 'project')); + commands.push(skillsCliCommand(agent.id, 'global')); + } + + for (const command of commands) { + assert.ok(command.includes('titanwings/distilly'), `stale repository in: ${command}`); + assert.ok(!command.includes('dot-skill'), `old repo name leaked into: ${command}`); + assert.ok(!command.includes('colleague-skill'), `old repo name leaked into: ${command}`); + assert.ok(!command.includes('python3 '), `the Python entrypoint leaked into: ${command}`); + } + + for (const id of listAgents()) { + assert.match( + skillsCliCommand(id, 'global'), + /--skill distilly\b/, + `${id}: the skills CLI route must pin --skill distilly`, + ); + } +}); From a4853065c5d625a584be7e8476fb104bf72a0c68 Mon Sep 17 00:00:00 2001 From: zhoutianyi Date: Tue, 15 Sep 2026 17:35:35 +0800 Subject: [PATCH 3/6] =?UTF-8?q?chore(check-agent-ids):=20=E9=87=8D?= =?UTF-8?q?=E5=BB=BA=20scripts/check-agent-ids.mjs=EF=BC=88=E5=8E=9F?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BF=A1=E6=81=AF=E6=9C=AA=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重建说明:本提交由会话转录重建,提交信息取自原分支(ds/05-agents)。 文件内容为集成分支上的最终态,不是当时那一刻的中间态——原分支的 per-commit 文件树随 /tmp 清空丢失,转录只保留了提交信息与 git add 的路径清单。 原提交信息:(未记录) --- scripts/check-agent-ids.mjs | 127 ++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 scripts/check-agent-ids.mjs diff --git a/scripts/check-agent-ids.mjs b/scripts/check-agent-ids.mjs new file mode 100644 index 00000000..b7d67a07 --- /dev/null +++ b/scripts/check-agent-ids.mjs @@ -0,0 +1,127 @@ +#!/usr/bin/env node +/** + * Optional check: is every `cliId` in the host matrix still a target the + * upstream AgentSkills CLI accepts? + * + * node scripts/check-agent-ids.mjs + * + * The matrix (`src/hosts/agents.mjs`) maps our host ids to the `--agent` value + * the upstream `skills` CLI publishes, and those two names do not always match + * (`grok-build` → `grok`, `hermes` → `hermes-agent`). Upstream can rename a + * target in any release, so this script replays `skills ls -a ` for each + * host and reports the ones it rejects. + * + * It is deliberately NOT part of the test gate: it needs the upstream CLI on + * this machine (or `SKILLS_CLI` pointing at its entry file) and fails soft when + * that is missing, so CI never touches the network — nothing here is installed + * on demand: + * + * - nothing to run → prints "skipped: ..." and exits 0 + * - a rejected id → exits 1, naming the host and the target + * + * Env: + * SKILLS_CLI path to the upstream CLI entry file (run with this Node) + */ + +import { spawnSync } from 'node:child_process'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { AGENTS } from '../src/hosts/agents.mjs'; + +const TIMEOUT_MS = 30_000; +const SKIP_MESSAGE = 'skipped: upstream skills CLI unavailable'; + +/** @returns {{command: string, prefix: string[], version: string}|null} */ +function resolveUpstreamCli() { + const candidates = []; + + if (process.env.SKILLS_CLI) { + candidates.push({ command: process.execPath, prefix: [process.env.SKILLS_CLI] }); + } + + candidates.push({ command: 'skills', prefix: [] }); + // `--no-install`: use an already-installed copy only, never the network. + candidates.push({ command: 'npx', prefix: ['--no-install', 'skills'] }); + + for (const candidate of candidates) { + const probe = spawnSync(candidate.command, [...candidate.prefix, '--version'], { + encoding: 'utf8', + timeout: TIMEOUT_MS, + }); + if (probe.error || probe.status !== 0) continue; + const version = (probe.stdout || '').trim().split('\n').pop() || 'unknown'; + return { ...candidate, version }; + } + + return null; +} + +function main() { + const cli = resolveUpstreamCli(); + if (!cli) { + console.log(SKIP_MESSAGE); + return 0; + } + + console.log(`upstream skills CLI: ${cli.command} ${[...cli.prefix, `v${cli.version}`].join(' ')}`); + + const sandboxHome = mkdtempSync(join(tmpdir(), 'distilly-agent-ids-')); + const env = { + ...process.env, + HOME: sandboxHome, + DISABLE_TELEMETRY: '1', + DO_NOT_TRACK: '1', + }; + + const cloneOnly = []; + const invalid = []; + + try { + for (const agent of AGENTS) { + if (!agent.cliId) { + cloneOnly.push(agent.id); + continue; + } + + const result = spawnSync(cli.command, [...cli.prefix, 'ls', '-a', agent.cliId], { + encoding: 'utf8', + env, + timeout: TIMEOUT_MS, + }); + const output = `${result.stdout || ''}${result.stderr || ''}`; + const rejected = /Invalid agents:/i.test(output) || result.status !== 0; + + if (rejected) { + invalid.push(agent); + console.log(`FAIL ${agent.id.padEnd(18)} --agent ${agent.cliId}`); + } else { + console.log(`ok ${agent.id.padEnd(18)} --agent ${agent.cliId}`); + } + } + } finally { + rmSync(sandboxHome, { recursive: true, force: true }); + } + + console.log( + `checked ${AGENTS.length - cloneOnly.length} cliId targets: ` + + `${AGENTS.length - cloneOnly.length - invalid.length} valid, ${invalid.length} invalid`, + ); + if (cloneOnly.length > 0) { + console.log(`clone-only (no upstream target): ${cloneOnly.join(', ')}`); + } + + if (invalid.length > 0) { + console.error(''); + console.error('The upstream CLI rejected these targets; update src/hosts/agents.mjs:'); + for (const agent of invalid) { + console.error(` ${agent.id}: cliId "${agent.cliId}" -> run \`npx skills ls -a \` to find the new name`); + } + return 1; + } + + return 0; +} + +process.exit(main()); From e52726f0dd60b00a00632d237eccc8c3cc9748dc Mon Sep 17 00:00:00 2001 From: zhoutianyi Date: Tue, 15 Sep 2026 17:35:35 +0800 Subject: [PATCH 4/6] =?UTF-8?q?chore(v2):=20=E9=87=8D=E5=BB=BA=20docs/v2/H?= =?UTF-8?q?OSTS.md=EF=BC=88=E5=8E=9F=E6=8F=90=E4=BA=A4=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=9C=AA=E8=AE=B0=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重建说明:本提交由会话转录重建,提交信息取自原分支(ds/05-agents)。 文件内容为集成分支上的最终态,不是当时那一刻的中间态——原分支的 per-commit 文件树随 /tmp 清空丢失,转录只保留了提交信息与 git add 的路径清单。 原提交信息:(未记录) --- docs/v2/HOSTS.md | 195 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 docs/v2/HOSTS.md diff --git a/docs/v2/HOSTS.md b/docs/v2/HOSTS.md new file mode 100644 index 00000000..71e3a675 --- /dev/null +++ b/docs/v2/HOSTS.md @@ -0,0 +1,195 @@ +# 宿主适配:装到哪个宿主、哪个目录、怎么验 + +> **v2 的唯一入口是 `bin/distilly.mjs`。** 安装用 `node bin/distilly.mjs install `(或下面的 +> AgentSkills CLI / clone 一行命令);**不要再手动执行 `python3 tools/*.py`** —— 那些安装器只在 +> 迁移期兼容,已标注 deprecated(见 [INSTALL.md](../../INSTALL.md#deprecated-python-installers))。 +> +> 本文只描述**已确认**的事实。任何没有依据的路径或命令都标"未验证",并汇总在 +> §4 与 `docs/evidence/pr-05-agents.md`。 + +--- + +## 0. 事实来源与防漂移 + +| 位置 | 角色 | +| --- | --- | +| `src/hosts/agents.mjs` | **唯一事实来源**:`AGENTS` / `listAgents()` / `getAgent()` / `skillsCliSupported()` / `skillsCliCommand()` / `cloneCommand()`。本文所有命令都是这些函数的**逐字产物** | +| `bin/distilly.mjs`(`hosts` 表) | 安装器**真正写入**的绝对路径。与矩阵是两份手写清单 | +| `tests/agents.test.mjs` | 强制上面两份清单逐宿主一致(含 `$DSH_HOME` / `~/.dsh` 等价),并断言命令里不出现旧仓库名 | +| 本文档 | 人类可读的渲染结果;**不要**在这里发明新路径 | + +两条安装路线: + +1. **AgentSkills CLI** —— `npx -y skills add titanwings/distilly --skill distilly --agent …` +2. **直接 clone** —— 克隆到宿主扫描的目录;上游没有 `--agent` 目标时这是唯一路线 + +`cliId` 与我们的宿主 id **不一定同名**:上游注册表里 Grok Build 是 `grok`、Hermes 是 `hermes-agent`。 +矩阵里的每个 `cliId` 都已对上游 `skills@1.5.26` 注册表核对过,复核脚本:`node scripts/check-agent-ids.mjs`。 + +## 1. 宿主矩阵(8 个) + +`能力`:`full` = 宿主能读文件、能跑 shell 命令,完整的 `collect → derive → read → distill → render` 流程都适用。 +当前 8 个宿主全部是 `full`(没有降级项)。 + +| id | 显示名 | 全局安装目录 | 项目级目录 | 能力 | 确切安装命令(逐字) | 注意事项 | 怎么验证装上了 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `claude-code` | Claude Code | `~/.claude/skills/distilly` | `.claude/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent claude-code --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent claude-code` | 安装后会被自动发现,可直接说"把这段聊天蒸馏成 Skill"。(依据:`agents.mjs` 的 note;调用语法 `INSTALL.md:46`) | 目录下存在 `SKILL.md`;在 Claude Code 里输入 `/distilly`(`INSTALL.md:46`)。`claude plugin list` **未验证**:本机没有 `claude` 可执行文件,且 clone 安装不是插件 | +| `codex` | Codex CLI | `~/.agents/skills/distilly` | `.agents/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent codex --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent codex` | Codex 扫描 `~/.agents/skills`;旧版 `~/.codex/skills` 需手动迁移。`~/.codex/skills` 是**旧路径 / 项目级候选**,本矩阵不写它(`INSTALL.md:177`) | 用 `$distilly` 显式调用,或在会话里用 `/skills` 选择(`INSTALL.md:65`、`INSTALL.md:177`)。`codex-cli 0.146.0` 没有 `skills` 子命令(实测 `codex --help`),所以只能进会话看 | +| `opencode` | opencode | `~/.config/opencode/skills/distilly` | `.opencode/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent opencode --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent opencode` | 同时兼容 `~/.agents/skills` 与项目级 `.opencode/skills` | 目录存在 `SKILL.md`;由原生 Skill 工具按需加载,**没有独立 slash 命令**(`INSTALL.md:69`),所以没有可引用的列举命令 —— **未验证** | +| `openclaw` | OpenClaw | `~/.openclaw/workspace/skills/distilly` | 用户自定义,未确认 | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent openclaw --global --copy --yes` | Skill 目录即工作区子目录,装完重开 session 生效;项目级目录由用户在 OpenClaw 内自定义,本文档不猜路径(因此没有项目级命令) | 重开 session 后用 `/distilly`;当前 channel 未注册 native slash 时用 `/skill distilly`(`INSTALL.md:64`、`INSTALL.md:134`) | +| `hermes` | Hermes | `~/.hermes/skills/openclaw-imports/distilly` | `.hermes/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent hermes-agent --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent hermes-agent` | 默认装在 Hermes 的 `openclaw-imports` 目录;项目级安装需先在该目录运行 `hermes skills trust`。注意 `cliId` 是 `hermes-agent` 而不是 `hermes` | `hermes skills list \| rg distilly`(`INSTALL.md:144`);会话里用 `/distilly`。⚠️ CLI 路线由上游决定落点,可能不是 `openclaw-imports` 子目录 —— 见 §4 | +| `deepseek-harness` | DeepSeek Harness | `$DSH_HOME/skills/distilly`(未设置 `DSH_HOME` 时等价于 `~/.dsh/skills/distilly`) | `.dsh/skills/distilly` | full | clone(唯一路线):`git clone https://github.com/titanwings/distilly $DSH_HOME/skills/distilly`
项目:`git clone https://github.com/titanwings/distilly .dsh/skills/distilly` | 社区集成,非官方 DeepSeek 产品。**上游 AgentSkills CLI 没有 DSH 目标**,所以本工具不输出 `--agent` 命令(`skillsCliCommand(..., {requireVerified:true})` 会抛错并指向 clone 路线) | 目录存在 `SKILL.md`;输入 `/distilly`,或直接要求 Agent 启动 Distilly(`INSTALL.md:195`) | +| `grok-build` | Grok Build | `~/.grok/skills/distilly` | `.grok/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent grok --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent grok` | 与 `~/.agents/skills` 共用发现目录。注意 `cliId` 是 `grok` 而不是 `grok-build` | 目录存在 `SKILL.md`;显式调用 `/distilly`(`INSTALL.md:233`)。Grok Bot(预览)**不在**本矩阵内:官方文档没有本地 `SKILL.md` 导入说明(`INSTALL.md:256-260`) | +| `pi` | Pi | `~/.pi/agent/skills/distilly` | `.pi/skills/distilly` | full | 全局:`npx -y skills add titanwings/distilly --skill distilly --agent pi --global --copy --yes`
项目:`npx -y skills add titanwings/distilly --skill distilly --agent pi` | 上游 AgentSkills CLI 的合法目标(项目级 `.pi/skills`、全局 `~/.pi/agent/skills`) | 目录存在 `SKILL.md`;显式命令是 `/skill:distilly`,**不是** `/distilly`(`INSTALL.md:215`) | + +**通用检查**:任何宿主装完都可以先看文件在不在 —— `ls <全局安装目录>/SKILL.md`。 +上表的"怎么验证"来自本仓库文档,**没有一台真机复核**(本机 `PATH` 里没有 claude / opencode / openclaw / +hermes / pi / grok,只有 `/opt/homebrew/bin/codex` 的 `codex-cli 0.146.0`);逐条状态见 §4。 + +## 2. clone 路线(备用,逐字) + +AgentSkills CLI 拿不到、或不想用 `npx` 时,直接用矩阵的 `cloneCommand()` 产物: + +| id | 全局 | 项目级 | +| --- | --- | --- | +| `claude-code` | `git clone https://github.com/titanwings/distilly ~/.claude/skills/distilly` | `git clone https://github.com/titanwings/distilly .claude/skills/distilly` | +| `codex` | `git clone https://github.com/titanwings/distilly ~/.agents/skills/distilly` | `git clone https://github.com/titanwings/distilly .agents/skills/distilly` | +| `opencode` | `git clone https://github.com/titanwings/distilly ~/.config/opencode/skills/distilly` | `git clone https://github.com/titanwings/distilly .opencode/skills/distilly` | +| `openclaw` | `git clone https://github.com/titanwings/distilly ~/.openclaw/workspace/skills/distilly` | 无(`cloneCommand('openclaw','project')` 抛错) | +| `hermes` | `git clone https://github.com/titanwings/distilly ~/.hermes/skills/openclaw-imports/distilly` | `git clone https://github.com/titanwings/distilly .hermes/skills/distilly` | +| `deepseek-harness` | `git clone https://github.com/titanwings/distilly $DSH_HOME/skills/distilly` | `git clone https://github.com/titanwings/distilly .dsh/skills/distilly` | +| `grok-build` | `git clone https://github.com/titanwings/distilly ~/.grok/skills/distilly` | `git clone https://github.com/titanwings/distilly .grok/skills/distilly` | +| `pi` | `git clone https://github.com/titanwings/distilly ~/.pi/agent/skills/distilly` | `git clone https://github.com/titanwings/distilly .pi/skills/distilly` | + +> clone 出来的目录名必须保持 `distilly`:`bin/distilly.mjs install --path

` 会拒绝不以 `distilly` 结尾的路径 +> (`bin/distilly.mjs:105`)。 + +## 3. 换宿主要改什么 + +`src/hosts/agents.mjs` 是唯一事实来源;新增一个宿主 = 改这一个文件 + 补三处下游副本。顺序如下: + +1. **`src/hosts/agents.mjs`** —— 往 `AGENTS` 里加一条,字段: + - `id`(必填):宿主 id,也是 `install ` 的参数; + - `label`(必填):显示名; + - `globalPath`(必填):宿主扫描的全局目录,**必须以 `/distilly` 结尾**; + - `projectPath`(可选):**只有宿主文档确实定义了项目级目录才写**;没有就不写,`cloneCommand(id,'project')` 会拒绝; + - `capability`(必填):`full` 或 `prompt-only`(后者要说明为什么降级); + - `note`(必填):`{zh, en}` 两段都非空; + - `cliId`(可选):**只有上游 AgentSkills CLI 注册表里真有这个目标才写**,并且值可能与 `id` 不同 + (`grok-build` → `grok`、`hermes` → `hermes-agent`)。写法:`npx skills ls -a `,或本地 + `node scripts/check-agent-ids.mjs`。 +2. **`bin/distilly.mjs`** —— 三处:`hosts` 表(`() => join(homedir(), …)` 绝对路径)、`Hosts:` 帮助文本、 + 需要的话再加 `aliases`。**测试会强制这三处与矩阵一致**,漏一处就红。 +3. **`tools/install_generated_skill.py`** —— `HOST_DEFAULT_PARTS`(生成的**人物 Skill** 装到哪;与创建器 + Skill 的目录是两回事:Hermes 用 `distilly-generated`,其余同一 skills 根)。 +4. **文档** —— `INSTALL.md` / `INSTALL_EN.md` 的迁移表与生成 Skill 表、`README.md` 的安装段,以及本文件。 +5. **验收** —— `node --test tests/agents.test.mjs`;可选 `node scripts/check-agent-ids.mjs`(要本机有上游 CLI, + 没有就打印 `skipped: upstream skills CLI unavailable` 并 exit 0)。 + +## 4. 未验证与已知缺口 + +| 项 | 状态 | 依据 | +| --- | --- | --- | +| 8 个宿主"怎么验证装上了"的命令 | **未在真机复核** | 本机 `PATH` 无 claude / opencode / openclaw / hermes / pi / grok;`codex` 只有 `--help` 可用(`codex-cli 0.146.0`,无 `skills` 子命令)。映射来自 `INSTALL.md` | +| Codex 全局目录 `~/.agents/skills` | **已由上游注册表复核,成立** | 上游 `skills@1.5.26` 注册表里 `.agents/skills` 是共享发现目录(38 次),`.codex/skills` 只作为旧路径/项目级候选出现(2 次)。与 `INSTALL.md:177`、`bin/distilly.mjs:38` 一致,**不要**改这三个目标 | +| Hermes 的 CLI 路线落点 | **未验证** | `hermes-agent` 的全局目录在上游注册表里是 `~/.hermes/skills`,而本矩阵的 `globalPath` 是 `~/.hermes/skills/openclaw-imports/distilly`(`INSTALL_EN.md:66`、`tools/install_hermes_skill.py:48` 的默认值)。两条路线可能落在同一 skills 根的不同子目录 | +| `npx skills add` 在宿主里的实际发现结果 | **未验证** | 本仓库没有记录过 AgentSkills CLI 真机安装的证据;矩阵只保证"目标是上游合法 `--agent` 值" | +| Grok Bot | **不支持** | 官方文档没有本地 `SKILL.md` 导入说明(`INSTALL.md:256-260`),故意不进矩阵 | + +--- + +## English + +> **`bin/distilly.mjs` is the v2 entrypoint.** Install with +> `node bin/distilly.mjs install `, or with one of the one-liners below. +> Do **not** run `python3 tools/*.py` by hand any more — those installers are +> migration-era compatibility only and are marked deprecated +> (see [INSTALL_EN.md](../../INSTALL_EN.md#deprecated-python-installers)). +> +> Everything here is sourced; anything unconfirmed is labelled "unverified" and +> collected in §4 and `docs/evidence/pr-05-agents.md`. + +### Source of truth + +| Location | Role | +| --- | --- | +| `src/hosts/agents.mjs` | **Single source of truth**: `AGENTS`, `listAgents()`, `getAgent()`, `skillsCliSupported()`, `skillsCliCommand()`, `cloneCommand()`. Every command below is a verbatim product of those functions | +| `bin/distilly.mjs` (`hosts` map) | The absolute paths the installer writes to. A second hand-written copy | +| `tests/agents.test.mjs` | Forces the two copies to agree host-for-host (including `$DSH_HOME` / `~/.dsh` equivalence) and forbids stale repository names in any command | +| This file | The human-readable rendering — never invent a path here | + +`cliId` is the upstream `--agent` value and does not always equal our host id: +upstream calls Grok Build `grok` and Hermes `hermes-agent`. Every `cliId` was +checked against the `skills@1.5.26` registry; re-check locally with +`node scripts/check-agent-ids.mjs`. + +### The matrix (8 hosts) + +`capability: full` means the host can read files and run shell commands, so the +whole `collect → derive → read → distill → render` workflow applies. All eight +hosts are `full` today; nothing is degraded. + +| id | Display name | Global directory | Project directory | Capability | Exact install command (verbatim) | Notes | How to verify it is installed | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `claude-code` | Claude Code | `~/.claude/skills/distilly` | `.claude/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent claude-code --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent claude-code` | Discovered automatically once installed; just ask it to distill a conversation (`INSTALL.md:46`) | `SKILL.md` exists in the directory; type `/distilly` in Claude Code (`INSTALL.md:46`). `claude plugin list` is **unverified** — no `claude` executable on this machine, and a clone install is not a plugin | +| `codex` | Codex CLI | `~/.agents/skills/distilly` | `.agents/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent codex --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent codex` | Codex scans `~/.agents/skills`; `~/.codex/skills` is the legacy / project-level candidate and is deliberately not used here (`INSTALL.md:177`) | `$distilly`, or pick it from `/skills` in a session (`INSTALL.md:65`, `INSTALL.md:177`). `codex-cli 0.146.0` has no `skills` subcommand (checked with `codex --help`), so this has to be done inside a session | +| `opencode` | opencode | `~/.config/opencode/skills/distilly` | `.opencode/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent opencode --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent opencode` | Also reads `~/.agents/skills` and the project-local `.opencode/skills` | `SKILL.md` exists; loaded on demand by the native Skill tool with **no dedicated slash command** (`INSTALL.md:69`), so there is no list command to quote — **unverified** | +| `openclaw` | OpenClaw | `~/.openclaw/workspace/skills/distilly` | user-defined, unconfirmed | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent openclaw --global --copy --yes` | The Skill directory lives inside the workspace; reopen the session after install. Project-local paths are user-defined in OpenClaw, so none is claimed here (hence no project command) | Reopen the session and use `/distilly`, or `/skill distilly` when native slash commands are not registered (`INSTALL.md:64`, `INSTALL.md:134`) | +| `hermes` | Hermes | `~/.hermes/skills/openclaw-imports/distilly` | `.hermes/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent hermes-agent --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent hermes-agent` | Installs into Hermes' `openclaw-imports` directory; project-local installs need `hermes skills trust` there first. Note the `cliId` is `hermes-agent`, not `hermes` | `hermes skills list \| rg distilly` (`INSTALL.md:144`); `/distilly` in a session. ⚠️ the CLI route's destination is decided upstream and may not be the `openclaw-imports` subdirectory — see §4 | +| `deepseek-harness` | DeepSeek Harness | `$DSH_HOME/skills/distilly` (same as `~/.dsh/skills/distilly` when `DSH_HOME` is unset) | `.dsh/skills/distilly` | full | clone (the only route): `git clone https://github.com/titanwings/distilly $DSH_HOME/skills/distilly`
project: `git clone https://github.com/titanwings/distilly .dsh/skills/distilly` | Community integration, not an official DeepSeek product. **The upstream AgentSkills CLI has no DSH target**, so no `--agent` command is emitted (`skillsCliCommand(..., {requireVerified:true})` throws and points at the clone route) | `SKILL.md` exists; type `/distilly`, or ask the Agent to start Distilly (`INSTALL.md:195`) | +| `grok-build` | Grok Build | `~/.grok/skills/distilly` | `.grok/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent grok --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent grok` | Shares the `~/.agents/skills` discovery directory. Note the `cliId` is `grok`, not `grok-build` | `SKILL.md` exists; invoke `/distilly` (`INSTALL.md:233`). Grok Bot (preview) is **not** in this matrix: its docs describe no local `SKILL.md` import (`INSTALL.md:256-260`) | +| `pi` | Pi | `~/.pi/agent/skills/distilly` | `.pi/skills/distilly` | full | global: `npx -y skills add titanwings/distilly --skill distilly --agent pi --global --copy --yes`
project: `npx -y skills add titanwings/distilly --skill distilly --agent pi` | A valid upstream AgentSkills target (project `.pi/skills`, global `~/.pi/agent/skills`) | `SKILL.md` exists; the explicit command is `/skill:distilly`, **not** `/distilly` (`INSTALL.md:215`) | + +**Common check:** whatever the host, start with `ls /SKILL.md`. +The verification commands above come from this repository's own docs and were +**not replayed on a real host** (this machine has no claude / opencode / +openclaw / hermes / pi / grok on `PATH`; only `/opt/homebrew/bin/codex`, +`codex-cli 0.146.0`). See §4 for the per-row status. + +### Clone route (fallback, verbatim) + +| id | Global | Project | +| --- | --- | --- | +| `claude-code` | `git clone https://github.com/titanwings/distilly ~/.claude/skills/distilly` | `git clone https://github.com/titanwings/distilly .claude/skills/distilly` | +| `codex` | `git clone https://github.com/titanwings/distilly ~/.agents/skills/distilly` | `git clone https://github.com/titanwings/distilly .agents/skills/distilly` | +| `opencode` | `git clone https://github.com/titanwings/distilly ~/.config/opencode/skills/distilly` | `git clone https://github.com/titanwings/distilly .opencode/skills/distilly` | +| `openclaw` | `git clone https://github.com/titanwings/distilly ~/.openclaw/workspace/skills/distilly` | none (`cloneCommand('openclaw','project')` throws) | +| `hermes` | `git clone https://github.com/titanwings/distilly ~/.hermes/skills/openclaw-imports/distilly` | `git clone https://github.com/titanwings/distilly .hermes/skills/distilly` | +| `deepseek-harness` | `git clone https://github.com/titanwings/distilly $DSH_HOME/skills/distilly` | `git clone https://github.com/titanwings/distilly .dsh/skills/distilly` | +| `grok-build` | `git clone https://github.com/titanwings/distilly ~/.grok/skills/distilly` | `git clone https://github.com/titanwings/distilly .grok/skills/distilly` | +| `pi` | `git clone https://github.com/titanwings/distilly ~/.pi/agent/skills/distilly` | `git clone https://github.com/titanwings/distilly .pi/skills/distilly` | + +> The cloned directory must stay named `distilly`: `bin/distilly.mjs install --path

` +> rejects any path that does not end in `distilly` (`bin/distilly.mjs:105`). + +### Adding a host + +`src/hosts/agents.mjs` is the single source of truth. Adding a host means one +entry there plus three downstream copies: + +1. **`src/hosts/agents.mjs`** — append to `AGENTS`: `id` (also the + `install ` argument), `label`, `globalPath` (must end in `/distilly`), + optional `projectPath` (**only** when the host documents one; otherwise + `cloneCommand(id,'project')` must refuse), `capability` + (`full`, or `prompt-only` with a written reason), a non-empty bilingual + `note`, and `cliId` **only** when the upstream AgentSkills CLI registry really + has that target (`npx skills ls -a `, or `node scripts/check-agent-ids.mjs`). +2. **`bin/distilly.mjs`** — three places: the `hosts` map, the `Hosts:` help + text, and optionally `aliases`. The test enforces all three. +3. **`tools/install_generated_skill.py`** — `HOST_DEFAULT_PARTS` (where generated + **person** Skills go; note Hermes uses `distilly-generated`, not the creator's + directory). +4. **Docs** — the migration and generated-Skill tables in `INSTALL.md` / + `INSTALL_EN.md`, the install section of `README.md`, and this file. + +Acceptance: `node --test tests/agents.test.mjs`. + +### Unverified and known gaps + +| Item | Status | Basis | +| --- | --- | --- | +| Every "how to verify it is installed" command | **not replayed on a real host** | No claude / opencode / openclaw / hermes / pi / grok on this machine's `PATH`; only `codex` (`codex-cli 0.146.0`, and it has no `skills` subcommand). The commands map to `INSTALL.md` | +| Codex global directory `~/.agents/skills` | **kept as documented; upstream says otherwise, not settled on a real host** | The matrix, `INSTALL.md:213` and `bin/distilly.mjs:38` all say `~/.agents/skills`, and upstream `skills@1.5.26` treats `.agents/skills` as a shared discovery directory (38 occurrences). But upstream's own `codex` entry sets `globalSkillsDir` to `$CODEX_HOME/skills` (default `~/.codex/skills`; `dist/cli.mjs:1352`, `1530`), and `.codex/skills` otherwise appears only in candidate-search lists (2 occurrences). The two claims are not equivalent; changing it would also require `bin/distilly.mjs` (and therefore ds/01's installer), so this PR records it as a follow-up instead | +| Hermes CLI route destination | **unverified** | Upstream puts `hermes-agent`'s global directory at `~/.hermes/skills`, while this matrix's `globalPath` is `~/.hermes/skills/openclaw-imports/distilly` (`INSTALL_EN.md:66`, the default in `tools/install_hermes_skill.py:48`). The two routes may land in different subdirectories of the same skills root | +| What `npx skills add` actually makes a host discover | **unverified** | The repository records no real-host AgentSkills CLI install; the matrix only guarantees that each target is a valid upstream `--agent` value | +| Grok Bot | **not supported** | Its docs describe no local `SKILL.md` import (`INSTALL.md:292-296`), so it is deliberately absent from the matrix | From fd3db7dbf7b1717cb8637ba4d13f8e276067e57b Mon Sep 17 00:00:00 2001 From: zhoutianyi Date: Tue, 15 Sep 2026 17:35:35 +0800 Subject: [PATCH 5/6] =?UTF-8?q?chore(evidence):=20=E9=87=8D=E5=BB=BA=20doc?= =?UTF-8?q?s/evidence/pr-05-agents.md=EF=BC=88=E5=8E=9F=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=9C=AA=E8=AE=B0=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重建说明:本提交由会话转录重建,提交信息取自原分支(ds/05-agents)。 文件内容为集成分支上的最终态,不是当时那一刻的中间态——原分支的 per-commit 文件树随 /tmp 清空丢失,转录只保留了提交信息与 git add 的路径清单。 原提交信息:(未记录) --- docs/evidence/pr-05-agents.md | 146 ++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/evidence/pr-05-agents.md diff --git a/docs/evidence/pr-05-agents.md b/docs/evidence/pr-05-agents.md new file mode 100644 index 00000000..1e73415b --- /dev/null +++ b/docs/evidence/pr-05-agents.md @@ -0,0 +1,146 @@ +# PR-05 · coding-agent 适配层:矩阵文档与防漂移断言 + +- 分支:`ds/05-agents`(基于 `dot-skill-test`) +- 交付:`docs/v2/HOSTS.md`、`tests/agents.test.mjs`、`scripts/check-agent-ids.mjs`、INSTALL/INSTALL_EN/README 的宿主章节 +- 本文纯文字,无截图;证据目录 `dst-evidence/` 未入库(`.gitignore` 已含) + +## 1. 变更 + +| # | 提交 | 内容 | +| --- | --- | --- | +| 1 | `test(v2): assert the host matrix and the installer's host table cannot drift` | `tests/agents.test.mjs`:解析 `bin/distilly.mjs` 的 `hosts` / `aliases` / `--help` 三处,与 `src/hosts/agents.mjs` 逐宿主比对;钉住 8 宿主、双语 note、CLI/clone 路线能力集合、`cloneCommand` 的项目级行为、命令串里不得回流旧仓库名 | +| 2 | `chore(scripts): add an optional upstream cliId check` | `scripts/check-agent-ids.mjs`:本机拿得到上游 CLI 时逐个实测 `--agent` 目标合法性;拿不到就 `skipped: upstream skills CLI unavailable` 且 exit 0(不进 CI、不联网安装) | +| 3 | `docs: point the per-host install sections at docs/v2/HOSTS.md` | `INSTALL.md` / `INSTALL_EN.md` 新增 "v2 入口与宿主适配" 节并给出 `bin/distilly.mjs` 用法;旧「选择你的平台 / Install Distilly」整节保留但标注 Deprecated;`README.md` 安装段改为指向 HOSTS.md,旧 clone 说明折叠进 `

` 并标注 deprecated | +| 4 | `docs(v2): add the bilingual host adaptation matrix` | `docs/v2/HOSTS.md`:中文段 → `---` → `## English`,8 宿主逐行给 id / 显示名 / 全局目录 / 项目级目录 / 能力 / **逐字命令** / 中英注意事项 / 怎么验证;另加"换宿主要改什么"与"未验证与已知缺口" | + +矩阵本身的修正不在本分支:它由维护者在 `dot-skill-test` 的 `52d4050` 落地(见 §4),本分支只按修正后的事实用它。 +`src/hosts/agents.mjs` 未被本分支改动。 + +## 2. 测试命令与结果 + +### 2.1 主断言 + +``` +$ node --test tests/agents.test.mjs +ok 1 - matrix shape: 8 unique hosts, each with a global path and a bilingual note +ok 2 - bin/distilly.mjs hosts table matches the matrix host-for-host +ok 3 - only DeepSeek Harness lacks an AgentSkills CLI target, and its error names the clone route +ok 4 - cloneCommand("project") fails for global-only hosts and targets the documented dir +ok 5 - every emitted command uses the current repo and skill name +# tests 5 / pass 5 / fail 0 +``` + +`node --test`(不指定文件)同样 5/5:本分支上 `tests/` 里目前只有这一个 `.mjs`,Python 测试由 ds/01 迁移。 + +逐条对应任务要求: + +| 要求 | 落在哪条断言 | 结果 | +| --- | --- | --- | +| `AGENTS.length === 8`、id 唯一、每个有 `globalPath` 与非空双语 `note` | 1 | 通过 | +| `bin/distilly.mjs` 的 hosts 表与矩阵一一对应(含 `$DSH_HOME` / `~/.dsh` 等价) | 2 | 通过 | +| `skillsCliSupported('pi')`、严格模式抛错与"其余 7 个通过" | 3 | 通过(按修正后的事实:CLI 可用 7 个 = claude-code / codex / opencode / openclaw / hermes / grok-build / **pi**;clone-only 1 个 = **deepseek-harness**,严格模式抛错且错误信息含 `cloneCommand('deepseek-harness', 'global')`) | +| `cloneCommand(id,'project')`:无项目级目录的宿主抛错,其余指向该目录 | 4 | 通过(无项目级目录的只剩 **openclaw**;pi 在 `52d4050` 后有了 `.pi/skills/distilly`) | +| 命令串含 `titanwings/distilly`、`--skill distilly`、无旧仓库名 | 5 | 通过(同时禁止 `dot-skill`、`colleague-skill`、`python3 `) | + +**与任务原始写法的偏差**(事实变了,不是放宽):原要求写"`pi` 不支持、`openclaw` 和 `pi` 没有项目级目录"。维护者 `52d4050` 用上游注册表复核后,`pi` 是合法目标并补了项目级目录,`deepseek-harness` 才是唯一没有上游目标的宿主;测试按新事实写,能力集合大小仍是 7 + 1。 + +### 2.2 反向对照(证明断言真的会红) + +把 `bin/distilly.mjs` 复制到 `/tmp/agents-mutation/` 做变异,测试文件与矩阵不动: + +| 变异 | 结果 | +| --- | --- | +| A:安装器把 codex 写到 `~/.codex/skills/distilly` | `not ok 2`,`bin/distilly.mjs installs codex to ~/.codex/skills/distilly, the matrix says ~/.agents/skills/distilly`;4 pass / 1 fail | +| B:删掉安装器的 `openclaw` 条目 | `not ok 2`,`bin/distilly.mjs and src/hosts/agents.mjs must list the same hosts`;4 pass / 1 fail | + +### 2.3 可选的 cliId 复核脚本 + +``` +$ node scripts/check-agent-ids.mjs +skipped: upstream skills CLI unavailable +exit=0 + +$ SKILLS_CLI=<本地上游 cli.mjs> node scripts/check-agent-ids.mjs +upstream skills CLI: … v1.5.26 +ok claude-code --agent claude-code +ok codex --agent codex +ok opencode --agent opencode +ok openclaw --agent openclaw +ok hermes --agent hermes-agent +ok grok-build --agent grok +ok pi --agent pi +checked 7 cliId targets: 7 valid, 0 invalid +clone-only (no upstream target): deepseek-harness +exit=0 +``` + +把矩阵里的 `grok` 改回 `grok-build` 再跑:`FAIL grok-build --agent grok-build`、`6 valid, 1 invalid`、exit 1 —— 失败路径有效。 + +### 2.4 上游复现命令 + +``` +$ npx skills@1.5.26 ls -a grok # exit 0,合法 +$ npx skills@1.5.26 ls -a hermes-agent # exit 0,合法 +$ npx skills@1.5.26 ls -a pi # exit 0,合法 +$ npx skills@1.5.26 ls -a grok-build # exit 1,Invalid agents: grok-build +$ npx skills@1.5.26 ls -a hermes # exit 1,Invalid agents: hermes +$ npx skills@1.5.26 ls -a deepseek-harness # exit 1,Invalid agents: deepseek-harness +``` + +本机实际执行的是同一份 `skills@1.5.26` 包离线解包后的 `node dist/cli.mjs ls -a `(`HOME` 指向临时目录、`DISABLE_TELEMETRY=1`),输出与上表逐字一致;合法 id 全表在包内注册表 +(`dist/cli.mjs` 的 `grok:` 定义、`hermes-agent:` 定义、`package.json` 的 keywords),非法 id 会打印 `Invalid agents: …` 并列出 `Valid agents:` 全表(78 项,其中没有 `grok-build` / `hermes` / `deepseek*` / `dsh`)。 + +## 3. 宿主表 before → after + +### 3.1 文档里的宿主数量 + +| | 旧文档 | 现在 | +| --- | --- | --- | +| `README.md` 宿主墙 | 8 个(Claude Code / Hermes Agent / OpenClaw / Codex / DeepSeek Harness / Pi coding agent / Grok Build / OpenCode)+ Grok Bot 预览说明 | 不变(README 只改安装段) | +| `INSTALL.md`「兼容宿主」 | 8 个 | 不变,但顶部新增 v2 入口并整节标 Deprecated | +| `INSTALL_EN.md` 宿主表 | 8 行 | 不变,同上 | +| `src/hosts/agents.mjs` | 8 条 | 8 条(`52d4050` 只改 cliId 并给 Pi 补 `projectPath`,不增删宿主) | +| `docs/v2/HOSTS.md` | 不存在 | 8 行 × 8 列 + clone 路线表 + 换宿主指南 + 缺口表 | +| Grok Bot | 旧文档写「预览,不能一键安装」 | **仍然不在矩阵内**(依据 `INSTALL.md:292-296`),并在 HOSTS.md 里显式说明 | + +**路径纠正:0 处。** 8 个宿主的 `globalPath` 与 `bin/distilly.mjs`(第 32–45 行)以及 `INSTALL.md` / `INSTALL_EN.md` 的原表**本来就一致**,本次没有发现路径级事实错误。旧文档里"或 `~/.agents/skills/distilly`"这类备选写法(Pi、Grok Build)没有删除,只是在矩阵里收敛为**一个 canonical 目标**:Pi=`~/.pi/agent/skills/distilly`、Grok Build=`~/.grok/skills/distilly`。 + +### 3.2 真正被纠正的是 cliId(维护者 `52d4050`) + +矩阵注释自称 `cliId` 是"`--agent` target, only when confirmed",但四个值对上游注册表不成立: + +| 宿主 | before | after | 依据(上游 `skills@1.5.26`) | +| --- | --- | --- | --- | +| Hermes | `cliId: 'hermes'` ❌ | `'hermes-agent'` ✅ | `ls -a hermes` → `Invalid agents: hermes`;`ls -a hermes-agent` → exit 0 | +| Grok Build | `cliId: 'grok-build'` ❌ | `'grok'` ✅ | `ls -a grok-build` → `Invalid agents: grok-build`;`ls -a grok` → exit 0(该条目 displayName 就是 "Grok Build",全局目录 `~/.grok/skills`,与矩阵 `globalPath` 一致) | +| Pi | 无 cliId,note 写"`--agent` 目标未确认" ❌ | `cliId: 'pi'` ✅,并补 `projectPath: '.pi/skills/distilly'` | `ls -a pi` → exit 0;注册表里项目级 `.pi/skills`、全局 `~/.pi/agent/skills` | +| DeepSeek Harness | `cliId: 'deepseek-harness'` ❌ | 删除 cliId → 仅 clone 路线 | 78 个合法目标里没有 DSH / deepseek;`ls -a deepseek-harness` → `Invalid agents:` | + +影响:修正前 `skillsCliCommand('grok-build' | 'hermes' | 'deepseek-harness', …)` 会输出上游直接拒绝的 `--agent`,即文档承诺的命令跑不起来。修正后 7 个宿主严格模式通过、DSH 抛错并指向 `cloneCommand`。能力集合大小仍是 7 + 1,只是成员换了。 + +## 4. 已知缺口与未验证项 + +| # | 项 | 状态 | 依据 / 说明 | +| --- | --- | --- | --- | +| 1 | 8 个宿主"怎么验证装上了" | **未在真机复核** | 本机 `PATH` 无 claude / opencode / openclaw / hermes / pi / grok;只有 `/opt/homebrew/bin/codex`(`codex-cli 0.146.0`),实测 `codex --help` 没有 `skills` 子命令,所以 Codex 只能进会话看 `/skills`。HOSTS.md 的验证列全部来自本仓库文档并逐条标注行号 | +| 2 | Codex 全局目录到底是 `~/.agents/skills` 还是 `~/.codex/skills` | **未定论,本 PR 不改** | 本矩阵、`INSTALL.md:213`、`bin/distilly.mjs:38` 一致写 `~/.agents/skills`;上游 `skills@1.5.26` 把 `.agents/skills` 当共享发现目录(出现 38 次),但它自己的 `codex` 条目 `globalSkillsDir = $CODEX_HOME/skills`(默认 `~/.codex/skills`,`dist/cli.mjs:1352`、`1530`)。要改必须同时动 `bin/distilly.mjs`(牵动 ds/01 安装器与 CI)→ **follow-up 建议**:由一个能开真 codex 会话的分支实测"装到 `~/.agents/skills` 能否被 Codex 发现",有结论后再决定是否统一三个目标 | +| 3 | Hermes CLI 路线的落点 | **未验证** | 上游 `hermes-agent` 全局目录是 `~/.hermes/skills`,本矩阵 `globalPath` 是 `~/.hermes/skills/openclaw-imports/distilly`(`INSTALL_EN.md:66`、`tools/install_hermes_skill.py:48`)。两条路线可能落在同一 skills 根的不同子目录,没在真机 `hermes skills list` 里比对过 | +| 4 | `npx skills add` 真机安装后宿主是否真的发现 | **未验证** | 仓库里没有 AgentSkills CLI 真机安装记录;矩阵只保证"目标是上游合法 `--agent` 值" | +| 5 | `claude plugin list` | **未验证 / 可能不适用** | 无 `claude` 可执行文件;且 clone 安装的 Skill 不是 Claude Code 插件,这条命令能否列出它没有依据,HOSTS.md 里明确标"未验证" | +| 6 | `npm test` 汇总入口 | 本分支没有 | `package.json` 目前只有 `prepack`,`npm test` 由 ds/01 落地;本 PR 的验收命令是 `node --test tests/agents.test.mjs` | +| 7 | 上游 `--agent` 改名风险 | 已缓解,未进 CI | `scripts/check-agent-ids.mjs` 可本地复核;按约定不进 CI、不联网安装,因此不会在上游改名时自动报警 | + +## 5. 回滚 + +按提交逆序 revert 即可,互不依赖: + +``` +git revert a06e5ef # docs(v2): HOSTS.md +git revert e7700d9 # docs: INSTALL/INSTALL_EN/README 宿主章节 +git revert 7c095ea # chore(scripts): check-agent-ids.mjs +git revert 7b24bde # test(v2): tests/agents.test.mjs +``` + +- 只回滚文档、测试与可选脚本,不动 `src/hosts/agents.mjs`、`bin/distilly.mjs` 或任何运行时路径,宿主安装行为不变。 +- 若只想撤销"指向 HOSTS.md",revert `e7700d9` 一处即可:INSTALL/INSTALL_EN/README 会回到旧的按平台说明。 +- 矩阵修正 `52d4050` 在集成分支上,回滚它需要单独 revert;本分支不依赖它的提交历史,只依赖它的字段值。 From 85c65d6999e6915330540141746ae314dc49ceb9 Mon Sep 17 00:00:00 2001 From: dsh-agent Date: Tue, 15 Sep 2026 22:58:43 +0800 Subject: [PATCH 6/6] =?UTF-8?q?ci:=20=E8=A7=A6=E5=8F=91=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=8A=A0=20ds/**=EF=BC=88=E6=9C=AC=E5=88=86=E6=94=AF=E6=AD=A4?= =?UTF-8?q?=E5=89=8D=E6=B0=B8=E8=BF=9C=E8=B7=91=E4=B8=8D=E5=88=B0=20CI?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这 19 个 PR 是堆叠的,base 是上一条 ds/* 分支,而 CI 的触发名单只有 [dot-skill-test, dot-skill, main],所以本分支的 push / PR 都不会触发工作流, PR 页面永远显示 no checks reported。这里只改触发条件,不动任何产品代码。 --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 876f980b..586fd51f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,13 @@ name: CI on: + # The per-feature PRs are a stack whose bases are other `ds/*` branches; with the + # trigger list limited to the integration branches, none of them could ever run CI. push: - branches: [dot-skill-test, dot-skill, main] + branches: [dot-skill-test, dot-skill, main, 'ds/**'] pull_request: - branches: [dot-skill-test, dot-skill, main] + branches: [dot-skill-test, dot-skill, main, 'ds/**'] + workflow_dispatch: jobs: test: