diff --git a/README.md b/README.md index 90df711..1130947 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Read first: skills-catalog.json, docs/SKILL_ROUTING_MATRIX.md, docs/AI_E2E_SCENA Then run: 1) ./bootstrap.sh --source github --dest ./downloaded-skills 2) bun run health:check -- --skills-root ./downloaded-skills -3) read `clientInstall.openclaw` / `clientInstall.ironclaw` from `skills-catalog.json`; if `installCommand` exists, execute it locally instead of treating a GitHub tree URL as the final install input +3) read `ironclawNative`, `clientInstall.openclaw`, and `clientInstall.ironclaw` from `skills-catalog.json`; for IronClaw, execute only `ironclawNative` and treat `clientInstall.ironclaw` as reserved metadata instead of a runnable fallback Routing rule: follow SKILL_ROUTING_MATRIX; if ambiguous, output Recommended/Alternative/Reason. Failure rule: use Common Recovery Template in docs/AI_E2E_SCENARIOS.md. ``` @@ -41,8 +41,9 @@ Treat skill distribution in two stages: Rules: 1. GitHub tree/repo URLs are discovery sources only. Do not treat them as the final IronClaw install artifact. -2. For IronClaw, prefer `clientInstall.ironclaw.installCommand` and expect a trusted local install step. +2. For IronClaw, use `ironclawNative` only. `clientInstall.ironclaw` is retained as a reserved compatibility field and must not be treated as an executable fallback. 3. For OpenClaw, prefer `ClawHub` / managed install when `distributionSources.clawhubId` exists; otherwise use `clientInstall.openclaw.installCommand`. +4. ClawHub may act as a discovery shell for IronClaw-native WASM installs; do not confuse that with the final write-capable runtime. ## Prerequisites @@ -131,11 +132,13 @@ Main fields per skill: 3. `repository.https` 4. `distributionSources` (`githubRepo`, `npmPackage`, optional `clawhubId`) 5. `description`, `capabilities` -6. `artifacts` (`skillMd`, `mcpServer`, `openclaw`) -7. `setupCommands` (compatibility display commands such as `claudeDesktop`, `cursor`, `openclaw`, `ironclaw`) +6. `artifacts` (`skillMd`, `mcpServer`, `openclaw`, `ironclawWasm`) +7. `setupCommands` (compatibility display commands such as `claudeDesktop`, `cursor`, `openclaw`) 8. `clientSupport` (support matrix such as `claude_desktop`, `cursor`, `ironclaw`, `codex`) -9. `clientInstall` (machine-executable activation contract for `openclaw` / `ironclaw`) -10. `dependsOn` (optional, schema `1.3.0`) +9. `clientInstall` (`openclaw` machine activation contract plus reserved `ironclaw` compatibility field) +10. `ironclawNative` (native WASM artifact contract for IronClaw) +11. `clawhub` (optional discovery-shell/runtime role metadata) +12. `dependsOn` (optional, schema `1.4.0`) Schema references: 1. `docs/schemas/workspace.schema.json` @@ -144,7 +147,7 @@ Schema references: 4. `docs/schemas/skills-catalog.schema.json` Schema evolution policy: -1. `patch` (`1.3.x`): wording/docs fixes, no field semantics change. +1. `patch` (`1.4.x`): wording/docs fixes, no field semantics change. 2. `minor` (`1.x.0`): backward-compatible field additions. 3. `major` (`x.0.0`): breaking changes only. @@ -166,9 +169,9 @@ This section is auto-synced by `bun run catalog:generate`. | aelfscan-skill | @aelfscan/agent-skills | 0.2.2 | 61 | AelfScan explorer data retrieval and analytics skill for agents. | | awaken-agent-skills | @awaken-finance/agent-kit | 1.2.4 | 11 | Awaken DEX trading and market data operations for agents. | | eforest-agent-skills | @eforest-finance/agent-skills | 0.4.3 | 48 | eForest symbol and forest NFT operations for agent workflows. | -| portkey-ca-agent-skills | @portkey/ca-agent-skills | 2.0.0 | 28 | Portkey CA wallet registration/auth/guardian/transfer operations for agents. | -| portkey-eoa-agent-skills | @portkey/eoa-agent-skills | 1.2.4 | 21 | Portkey EOA wallet and asset operations for aelf agents. | -| tomorrowdao-agent-skills | @tomorrowdao/agent-skills | 0.1.4 | 41 | TomorrowDAO governance, BP, and resource operations for agents. | +| portkey-ca-agent-skills | @portkey/ca-agent-skills | 2.3.0 | 32 | Portkey CA wallet registration/auth/guardian/transfer operations for agents. | +| portkey-eoa-agent-skills | @portkey/eoa-agent-skills | 1.2.6 | 21 | Portkey EOA wallet and asset operations for aelf agents. | +| tomorrowdao-agent-skills | @tomorrowdao/agent-skills | 0.2.0 | 44 | TomorrowDAO governance, BP, and resource operations for agents. | ## Health Check diff --git a/README.zh-CN.md b/README.zh-CN.md index 4ad82ae..9579cac 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -20,7 +20,7 @@ 然后执行: 1) ./bootstrap.sh --source github --dest ./downloaded-skills 2) bun run health:check -- --skills-root ./downloaded-skills -3) 读取 `skills-catalog.json` 里的 `clientInstall.openclaw` / `clientInstall.ironclaw`;若存在 `installCommand`,由宿主或 agent 在本地执行,而不是把 GitHub tree URL 当成最终安装输入 +3) 读取 `skills-catalog.json` 里的 `ironclawNative`、`clientInstall.openclaw` 与 `clientInstall.ironclaw`;对 IronClaw 只执行 `ironclawNative`,`clientInstall.ironclaw` 仅保留为兼容字段,不能当作可执行 fallback 路由规则:按 SKILL_ROUTING_MATRIX;若有歧义,输出 Recommended/Alternative/Reason。 失败规则:按 docs/AI_E2E_SCENARIOS.zh-CN.md 的常见错误恢复模板执行。 ``` @@ -41,8 +41,9 @@ Skill 分发一律拆成两个阶段: 固定规则: 1. GitHub tree/repo URL 只用于 discovery,不是 IronClaw 的最终安装载体。 -2. IronClaw 一律优先读取 `clientInstall.ironclaw.installCommand`,并预期存在 trusted 本地安装步骤。 +2. IronClaw 一律只走 `ironclawNative`;`clientInstall.ironclaw` 仅保留为兼容字段,不能再作为 activation fallback。 3. OpenClaw 若存在 `distributionSources.clawhubId`,优先走 `ClawHub` / managed install;否则回退到 `clientInstall.openclaw.installCommand`。 +4. ClawHub 对 IronClaw-native skill 可以只承担 discovery shell 角色,不能等同于最终写能力运行时。 ## 环境依赖 @@ -131,11 +132,13 @@ bun run update:check 3. `repository.https` 4. `distributionSources`(`githubRepo`, `npmPackage`,以及可选 `clawhubId`) 5. `description`, `capabilities` -6. `artifacts`(`skillMd`, `mcpServer`, `openclaw`) -7. `setupCommands`(兼容展示字段,例如 `claudeDesktop`、`cursor`、`openclaw`、`ironclaw`) +6. `artifacts`(`skillMd`, `mcpServer`, `openclaw`, `ironclawWasm`) +7. `setupCommands`(兼容展示字段,例如 `claudeDesktop`、`cursor`、`openclaw`) 8. `clientSupport`(支持矩阵,例如 `claude_desktop`、`cursor`、`ironclaw`、`codex`) -9. `clientInstall`(`openclaw` / `ironclaw` 的机器可执行安装契约) -10. `dependsOn`(可选,schema `1.3.0`) +9. `clientInstall`(`openclaw` 的机器安装契约,以及保留的 `ironclaw` 兼容字段) +10. `ironclawNative`(IronClaw native WASM artifact 契约) +11. `clawhub`(可选的 discovery-shell/runtime 角色元数据) +12. `dependsOn`(可选,schema `1.4.0`) Schema 参考: 1. `docs/schemas/workspace.schema.json` @@ -144,7 +147,7 @@ Schema 参考: 4. `docs/schemas/skills-catalog.schema.json` Schema 演进规则: -1. `patch`(`1.3.x`):文案/文档修订,不改变字段语义。 +1. `patch`(`1.4.x`):文案/文档修订,不改变字段语义。 2. `minor`(`1.x.0`):向后兼容的字段新增。 3. `major`(`x.0.0`):仅用于破坏性变更。 @@ -166,9 +169,9 @@ Schema 演进规则: | aelfscan-skill | @aelfscan/agent-skills | 0.2.2 | 61 | AelfScan 浏览器数据检索与分析技能。 | | awaken-agent-skills | @awaken-finance/agent-kit | 1.2.4 | 11 | Awaken DEX 交易与行情数据技能。 | | eforest-agent-skills | @eforest-finance/agent-skills | 0.4.3 | 48 | eForest 代币与 NFT 市场操作技能。 | -| portkey-ca-agent-skills | @portkey/ca-agent-skills | 2.0.0 | 28 | Portkey CA 钱包注册、认证、Guardian 与转账技能。 | -| portkey-eoa-agent-skills | @portkey/eoa-agent-skills | 1.2.4 | 21 | Portkey EOA 钱包与资产操作技能。 | -| tomorrowdao-agent-skills | @tomorrowdao/agent-skills | 0.1.4 | 41 | TomorrowDAO 治理、BP 与资源操作技能。 | +| portkey-ca-agent-skills | @portkey/ca-agent-skills | 2.3.0 | 32 | Portkey CA 钱包注册、认证、Guardian 与转账技能。 | +| portkey-eoa-agent-skills | @portkey/eoa-agent-skills | 1.2.6 | 21 | Portkey EOA 钱包与资产操作技能。 | +| tomorrowdao-agent-skills | @tomorrowdao/agent-skills | 0.2.0 | 44 | TomorrowDAO 治理、BP 与资源操作技能。 | ## 健康检查 diff --git a/docs/AI_E2E_SCENARIOS.md b/docs/AI_E2E_SCENARIOS.md index f6c80fa..110a859 100644 --- a/docs/AI_E2E_SCENARIOS.md +++ b/docs/AI_E2E_SCENARIOS.md @@ -71,7 +71,8 @@ Execution flow: - `docs/schemas/skill-frontmatter.schema.json` - `docs/schemas/openclaw.schema.json` - `docs/schemas/skills-catalog.schema.json` -3. If the skill supports MCP + setup, ensure catalog metadata exposes `setupCommands.ironclaw`, `clientSupport.ironclaw`, `distributionSources`, and `clientInstall`. +3. Ensure catalog metadata exposes `clientSupport.ironclaw`, `distributionSources`, and `clientInstall`, while keeping IronClaw activation bound to `ironclawNative`. +4. If the skill supports native IronClaw WASM, ensure catalog metadata also exposes `artifacts.ironclawWasm` and `ironclawNative`. 4. Add new path to `workspace.json` with `${SKILLS_BASE}` placeholder. 5. If dependency exists, add direct `dependsOn` entries. 6. Run gates in order: @@ -85,13 +86,14 @@ bun run security:audit 7. Prepare PR body with the 6 fixed sections from AI contract. Success criteria: -1. Catalog generation passes with schema `1.3.0`. +1. Catalog generation passes with schema `1.4.0`. 2. No gate failure (`health/readme/security/bootstrap`). 3. PR description contains Goal/Non-goal, key files, contract mapping, validation outputs, risk, rollback. Additional install-routing rule: 1. GitHub repo URLs are discovery-only. -2. For OpenClaw/IronClaw execution, prefer `clientInstall.*.installCommand` when present. +2. For OpenClaw execution, prefer `clientInstall.openclaw.installCommand` when present. +3. For IronClaw execution, use `ironclawNative` only; `clientInstall.ironclaw` is reserved compatibility metadata and must not be executed. ## Common Recovery Template diff --git a/docs/AI_E2E_SCENARIOS.zh-CN.md b/docs/AI_E2E_SCENARIOS.zh-CN.md index f60207a..d543b4e 100644 --- a/docs/AI_E2E_SCENARIOS.zh-CN.md +++ b/docs/AI_E2E_SCENARIOS.zh-CN.md @@ -71,7 +71,8 @@ - `docs/schemas/skill-frontmatter.schema.json` - `docs/schemas/openclaw.schema.json` - `docs/schemas/skills-catalog.schema.json` -3. 若 skill 支持 MCP + setup,确保 catalog 输出 `setupCommands.ironclaw`、`clientSupport.ironclaw`、`distributionSources` 与 `clientInstall`。 +3. 确保 catalog 输出 `clientSupport.ironclaw`、`distributionSources` 与 `clientInstall`,并把 IronClaw activation 固定绑定到 `ironclawNative`。 +4. 若 skill 支持 IronClaw native WASM,还要确保 catalog 输出 `artifacts.ironclawWasm` 与 `ironclawNative`。 4. 在 `workspace.json` 增加新路径,使用 `${SKILLS_BASE}` 占位。 5. 如存在依赖,补充直接依赖 `dependsOn`。 6. 按顺序执行门禁: @@ -85,13 +86,14 @@ bun run security:audit 7. 按契约固定 6 段格式整理 PR 描述。 成功标准: -1. catalog 成功生成且 schema 为 `1.3.0`。 +1. catalog 成功生成且 schema 为 `1.4.0`。 2. `health/readme/security/bootstrap` 门禁全部通过。 3. PR 描述包含 Goal/Non-goal、关键文件、契约映射、验证输出、风险与回滚。 补充安装规则: 1. GitHub repo URL 只用于 discovery。 -2. OpenClaw/IronClaw 执行时,优先读取 `clientInstall.*.installCommand`。 +2. OpenClaw 执行时,优先读取 `clientInstall.openclaw.installCommand`。 +3. IronClaw 执行时,只能走 `ironclawNative`;`clientInstall.ironclaw` 仅是兼容元数据,不能执行。 ## 常见错误恢复模板 diff --git a/docs/AI_SKILL_CONTRACT.md b/docs/AI_SKILL_CONTRACT.md index a747c87..3d2e129 100644 --- a/docs/AI_SKILL_CONTRACT.md +++ b/docs/AI_SKILL_CONTRACT.md @@ -55,14 +55,15 @@ AI output `MUST NOT` stop at high-level suggestions without concrete files and c - `MUST` provide MCP support: `src/mcp/server.ts` or `scripts.mcp`. - If OpenClaw native is declared, `MUST` include `openclaw.json` and satisfy `docs/schemas/openclaw.schema.json`. - If native setup is declared, `MUST` include `scripts.setup` or `bin/setup.ts|bin/setup.js`. -- If IronClaw native setup is declared, setup must support `bun run setup ironclaw` (or equivalent `bin/setup.ts ironclaw`). +- If native IronClaw delivery is declared, the repo should expose an `ironclaw-wasm/` sidecar plus GitHub Release artifacts and metadata in `ironclawNative`. - GitHub repo/tree URLs are discovery sources only; final activation for OpenClaw/IronClaw `MUST` be expressible through catalog metadata. 3. Generated catalog -- `MUST` produce schema version `1.3.0`. +- `MUST` produce schema version `1.4.0`. - `MUST` satisfy `docs/schemas/skills-catalog.schema.json`. - `MUST` include `dependsOn` in catalog when declared in workspace. - `MUST` emit `distributionSources` and `clientInstall` for published/installable skills. +- If native IronClaw delivery exists, `MUST` also emit `artifacts.ironclawWasm` and `ironclawNative`. 4. Documentation synchronization - If rule/contract/template changed, AI `MUST` update Chinese and English docs together. @@ -112,7 +113,7 @@ Optional full install validation: | Pattern | Cause | Repair | |---|---|---| | `[WARN] ... SKILL.md not found, project skipped` | Skill markdown missing | Add `SKILL.md` with valid front matter | -| `declared native-setup but setup command not available` | Missing setup entry | Add `scripts.setup` or `bin/setup.ts/js` | +| `ironclaw support must be native or unsupported in wasm-only rollout` | Invalid IronClaw support mode | Emit `clientSupport.ironclaw` as `native` or `unsupported` only | | `declared openclaw native but openclaw.json missing` | Missing OpenClaw config | Add `openclaw.json` | | `[FAIL] Duplicate skill id detected:` | Conflicting skill ids | Fix front matter `name`/workspace mapping | | `[FAIL] : dependsOn references unknown skill id` | Invalid dependency id | Fix `dependsOn` | @@ -122,7 +123,7 @@ Optional full install validation: ## 10. Definition of Done Done means all conditions below are met: -1. New skill appears in `skills-catalog.json` (schema `1.3.0`). +1. New skill appears in `skills-catalog.json` (schema `1.4.0`). 2. `health:check` has no fail. 3. `readme:check` passes. 4. `security:audit` passes. diff --git a/docs/AI_SKILL_CONTRACT.zh-CN.md b/docs/AI_SKILL_CONTRACT.zh-CN.md index 3dfa612..de6e636 100644 --- a/docs/AI_SKILL_CONTRACT.zh-CN.md +++ b/docs/AI_SKILL_CONTRACT.zh-CN.md @@ -55,14 +55,15 @@ AI 输出 `MUST NOT` 停留在高层建议而不落地到文件和命令。 - `MUST` 提供 MCP 支持:`src/mcp/server.ts` 或 `scripts.mcp`。 - 声明 OpenClaw native 时,`MUST` 存在 `openclaw.json` 且满足 `docs/schemas/openclaw.schema.json`。 - 声明 native setup 时,`MUST` 存在 `scripts.setup` 或 `bin/setup.ts|bin/setup.js`。 -- 若声明 IronClaw native setup,setup 入口必须支持 `bun run setup ironclaw`(或等价 `bin/setup.ts ironclaw`)。 +- 若声明 IronClaw native 交付,仓库还应提供 `ironclaw-wasm/` sidecar,以及通过 `ironclawNative` 暴露的 GitHub Release artifact 元数据。 - GitHub repo/tree URL 仅用于 discovery;OpenClaw/IronClaw 的最终 activation `MUST` 通过 catalog 元数据表达。 3. 生成产物 -- `MUST` 输出 schemaVersion `1.3.0` 的 catalog。 +- `MUST` 输出 schemaVersion `1.4.0` 的 catalog。 - `MUST` 满足 `docs/schemas/skills-catalog.schema.json`。 - 若 workspace 声明 `dependsOn`,catalog `MUST` 同步输出。 - 对外发布/可安装 skill,`MUST` 额外输出 `distributionSources` 与 `clientInstall`。 +- 若存在 IronClaw native 交付,`MUST` 额外输出 `artifacts.ironclawWasm` 与 `ironclawNative`。 4. 文档同步 - 若规则/契约/模板发生变更,AI `MUST` 同步更新中英文文档。 @@ -112,7 +113,7 @@ bun run security:audit | 输出模式 | 原因 | 修复 | |---|---|---| | `[WARN] ... SKILL.md not found, project skipped` | skill 缺少 markdown | 补齐 `SKILL.md` 与 front matter | -| `declared native-setup but setup command not available` | 缺 setup 入口 | 增加 `scripts.setup` 或 `bin/setup.ts/js` | +| `ironclaw support must be native or unsupported in wasm-only rollout` | IronClaw 支持级别非法 | 仅输出 `clientSupport.ironclaw = native|unsupported` | | `declared openclaw native but openclaw.json missing` | 缺 OpenClaw 配置 | 补齐 `openclaw.json` | | `[FAIL] Duplicate skill id detected:` | skill id 冲突 | 修正 front matter `name`/workspace 映射 | | `[FAIL] : dependsOn references unknown skill id` | 依赖 id 无效 | 修正 `dependsOn` | @@ -122,7 +123,7 @@ bun run security:audit ## 10. 完成定义(Definition of Done) 满足以下条件才算完成: -1. 新 skill 出现在 `skills-catalog.json`(schema `1.3.0`)。 +1. 新 skill 出现在 `skills-catalog.json`(schema `1.4.0`)。 2. `health:check` 无 fail。 3. `readme:check` 通过。 4. `security:audit` 通过。 diff --git a/docs/CATALOG_SCHEMA.md b/docs/CATALOG_SCHEMA.md index 9c16d27..2796ee3 100644 --- a/docs/CATALOG_SCHEMA.md +++ b/docs/CATALOG_SCHEMA.md @@ -1,6 +1,6 @@ [中文](CATALOG_SCHEMA.zh-CN.md) | English -# Catalog Schema Semantics (v1.3.0) +# Catalog Schema Semantics (v1.4.0) This document defines field semantics of `skills-catalog.json` for both AI and humans. @@ -8,7 +8,7 @@ This document defines field semantics of `skills-catalog.json` for both AI and h ```json { - "schemaVersion": "1.3.0", + "schemaVersion": "1.4.0", "generatedAt": "ISO-8601", "source": "workspace.json", "skills": [], @@ -17,7 +17,7 @@ This document defines field semantics of `skills-catalog.json` for both AI and h ``` Field meanings: -1. `schemaVersion`: schema version, currently `1.3.0`. +1. `schemaVersion`: schema version, currently `1.4.0`. 2. `generatedAt`: generation timestamp (UTC ISO string). 3. `source`: source file name used to build the catalog. 4. `skills`: skill entries. @@ -34,13 +34,15 @@ Each `skills[]` item includes: 6. `description`: high-level summary for routing. 7. `description_zh`: optional Chinese description, preferred by Chinese rendering and fallback to `description` when missing. 8. `capabilities`: short capability sentences for intent matching. -9. `artifacts`: boolean availability flags of required artifacts. +9. `artifacts`: boolean availability flags of required artifacts, including `ironclawWasm`. 10. `setupCommands`: compatibility display commands for humans/local repos. 11. `clientSupport`: support level matrix by client type, including `ironclaw`. -12. `clientInstall`: machine-executable activation contract for `openclaw` and `ironclaw`. -13. `openclawToolCount`: number of OpenClaw tools. -14. `dependsOn`: optional direct dependency skill id list for composition/order-aware execution. -15. `sourcePath`: local-only optional field, omitted in public catalog by default. +12. `clientInstall`: machine-executable activation contract for `openclaw`, plus a reserved compatibility slot for `ironclaw`. +13. `ironclawNative`: optional native IronClaw WASM artifact contract. +14. `clawhub`: optional ClawHub metadata describing discovery-shell vs runtime role. +15. `openclawToolCount`: number of OpenClaw tools. +16. `dependsOn`: optional direct dependency skill id list for composition/order-aware execution. +17. `sourcePath`: local-only optional field, omitted in public catalog by default. ## 3. `artifacts` semantics @@ -53,6 +55,9 @@ Each `skills[]` item includes: 3. `openclaw: true` - Means `openclaw.json` is present for OpenClaw tool descriptions. +4. `ironclawWasm: true` +- Means the repo ships an `ironclaw-wasm/` sidecar that can produce a native IronClaw WASM artifact. + ## 4. `distributionSources` and `clientInstall` 1. `distributionSources` @@ -66,8 +71,16 @@ Each `skills[]` item includes: - `package-setup`: host should execute `installCommand`, typically `bunx -p openclaw`. 3. `clientInstall.ironclaw` -- `trusted-local-install`: host should execute `installCommand`, typically `bunx -p ironclaw`. -- `requiresTrustPromotion: true` means the host must surface a trust prompt because trusted local files and MCP config will be written. +- Reserved compatibility field in the current wasm-only rollout. +- Hosts must treat `mode: unsupported` as the only executable-safe value today. +- Final IronClaw activation must come from `ironclawNative`, not `clientInstall.ironclaw`. +- `requiresTrustPromotion` is reserved for future rollout work and is not consumed today. + +4. `ironclawNative` +- Native IronClaw runtime contract for WASM tools. +- `artifactUrl` / `capabilitiesUrl` point at GitHub Release assets. +- `installCommand` is intentionally local-path based (for example `ironclaw tool install ./tool.wasm`); the host should download the assets first, then execute locally. +- `stateModel: isolated` means the native tool keeps its own IronClaw workspace state and does not share the Bun/MCP runtime store. ## 5. `clientSupport` enum semantics @@ -109,8 +122,9 @@ Recommended style: - Output: `skills-catalog.local.json` - Characteristic: includes `sourcePath`, intended for local machine only. -3. Additive note (1.3.0) -- Added `setupCommands.ironclaw` for trusted-skill setup guidance. -- Added `clientSupport.ironclaw` to the client support matrix. -- Added `distributionSources` and `clientInstall` to distinguish discovery from activation. +3. Additive note (1.4.0) +- Added `artifacts.ironclawWasm` to signal native IronClaw sidecars. +- Added `ironclawNative` for native WASM artifact discovery and install contracts. +- Added `clawhub` role metadata to distinguish discovery-shell vs runtime delivery. +- `clientInstall.ironclaw` remains in schema for backward compatibility, but the current rollout keeps it `unsupported`. - Existing consumers should ignore unknown fields if not needed. diff --git a/docs/CATALOG_SCHEMA.zh-CN.md b/docs/CATALOG_SCHEMA.zh-CN.md index 7080b1e..e7164d5 100644 --- a/docs/CATALOG_SCHEMA.zh-CN.md +++ b/docs/CATALOG_SCHEMA.zh-CN.md @@ -1,6 +1,6 @@ 中文 | [English](CATALOG_SCHEMA.md) -# Catalog Schema 语义说明(v1.3.0) +# Catalog Schema 语义说明(v1.4.0) 本文档定义 `skills-catalog.json` 的字段语义,供 AI 与开发者统一理解。 @@ -8,7 +8,7 @@ ```json { - "schemaVersion": "1.3.0", + "schemaVersion": "1.4.0", "generatedAt": "ISO-8601", "source": "workspace.json", "skills": [], @@ -17,7 +17,7 @@ ``` 字段说明: -1. `schemaVersion`:schema 版本。当前为 `1.3.0`。 +1. `schemaVersion`:schema 版本。当前为 `1.4.0`。 2. `generatedAt`:生成时间(UTC ISO 字符串)。 3. `source`:catalog 的输入来源文件名。 4. `skills`:技能数组。 @@ -34,13 +34,15 @@ 6. `description`:技能简介(用于高层路由)。 7. `description_zh`:可选中文描述。中文界面优先使用,缺失时回退 `description`。 8. `capabilities`:能力短句列表(用于 intent 匹配)。 -9. `artifacts`:能力产物存在性布尔值。 +9. `artifacts`:能力产物存在性布尔值,包含 `ironclawWasm`。 10. `setupCommands`:面向人类/本地仓的兼容展示命令。 11. `clientSupport`:客户端支持级别矩阵,包含 `ironclaw`。 -12. `clientInstall`:`openclaw` 与 `ironclaw` 的机器可执行 activation 契约。 -13. `openclawToolCount`:OpenClaw 工具数量。 -14. `dependsOn`:可选直接依赖 skill id 列表,用于编排顺序与组合执行。 -15. `sourcePath`:仅本地模式可选字段,公开 catalog 默认不含。 +12. `clientInstall`:`openclaw` 的机器 activation 契约,以及保留的 `ironclaw` 兼容字段。 +13. `ironclawNative`:可选的 IronClaw native WASM artifact 契约。 +14. `clawhub`:可选的 ClawHub 元数据,用于区分 discovery-shell 与 runtime 角色。 +15. `openclawToolCount`:OpenClaw 工具数量。 +16. `dependsOn`:可选直接依赖 skill id 列表,用于编排顺序与组合执行。 +17. `sourcePath`:仅本地模式可选字段,公开 catalog 默认不含。 ## 3. artifacts 语义 @@ -53,6 +55,9 @@ 3. `openclaw: true` - 意味着存在 `openclaw.json` 可用于 OpenClaw 工具描述。 +4. `ironclawWasm: true` +- 表示仓库带有 `ironclaw-wasm/` sidecar,可产出 IronClaw native WASM artifact。 + ## 4. `distributionSources` 与 `clientInstall` 1. `distributionSources` @@ -66,8 +71,16 @@ - `package-setup`:宿主应执行 `installCommand`,通常是 `bunx -p openclaw`。 3. `clientInstall.ironclaw` -- `trusted-local-install`:宿主应执行 `installCommand`,通常是 `bunx -p ironclaw`。 -- `requiresTrustPromotion: true` 表示宿主必须给出 trust 提示,因为会写入 trusted 本地 skill 与 MCP 配置。 +- 当前 wasm-only rollout 中,这个字段只作为兼容保留位。 +- 现阶段宿主只能把 `mode: unsupported` 当成安全可执行值。 +- IronClaw 的最终 activation 必须来自 `ironclawNative`,而不是 `clientInstall.ironclaw`。 +- `requiresTrustPromotion` 目前只是未来保留字段,当前 rollout 不消费。 + +4. `ironclawNative` +- IronClaw 原生 WASM runtime 契约。 +- `artifactUrl` / `capabilitiesUrl` 指向 GitHub Release assets。 +- `installCommand` 故意只表达本地路径安装(例如 `ironclaw tool install ./tool.wasm`);宿主需要先下载 artifact 再执行。 +- `stateModel: isolated` 表示 native tool 维护独立 IronClaw workspace 状态,不与 Bun/MCP runtime 共用。 ## 5. clientSupport 枚举语义 @@ -109,8 +122,9 @@ - 输出:`skills-catalog.local.json` - 特点:包含 `sourcePath`,仅适用于本机环境。 -3. 增量说明(1.3.0) -- 新增 `setupCommands.ironclaw`,用于表达 trusted skill setup 命令。 -- 新增 `clientSupport.ironclaw`,用于表达 IronClaw 支持级别。 -- 新增 `distributionSources` 与 `clientInstall`,用于区分 discovery 与 activation。 +3. 增量说明(1.4.0) +- 新增 `artifacts.ironclawWasm`,用于表达 IronClaw native sidecar。 +- 新增 `ironclawNative`,用于表达 native WASM artifact 发现与安装契约。 +- 新增 `clawhub` role 元数据,用于区分 discovery-shell 与 runtime 交付。 +- `clientInstall.ironclaw` 继续保留以兼容旧消费者,但当前 rollout 固定为 `unsupported`。 - 旧消费者如暂不使用新增字段,需按“忽略未知字段”兼容读取。 diff --git a/docs/examples/EXAMPLE_NEW_SKILL_DIFF.md b/docs/examples/EXAMPLE_NEW_SKILL_DIFF.md index 56752bd..0aa2ea1 100644 --- a/docs/examples/EXAMPLE_NEW_SKILL_DIFF.md +++ b/docs/examples/EXAMPLE_NEW_SKILL_DIFF.md @@ -24,7 +24,17 @@ This example shows the minimum complete change set for adding one skill. "id": "example-skill", "displayName": "Example Skill", "dependsOn": ["aelf-node-skill"], - "npm": { "name": "@example/skill", "version": "0.1.0" } + "npm": { "name": "@example/skill", "version": "0.1.0" }, + "artifacts": { "ironclawWasm": true }, + "ironclawNative": { + "runtime": "wasm-tool", + "distribution": "github-release", + "artifactUrl": "https://github.com/example/skill/releases/download/v0.1.0/example-skill.wasm", + "capabilitiesUrl": "https://github.com/example/skill/releases/download/v0.1.0/example-skill.capabilities.json", + "installCommand": "ironclaw tool install ./example-skill.wasm", + "stateModel": "isolated", + "stability": "experimental" + } } ``` @@ -33,7 +43,7 @@ This example shows the minimum complete change set for adding one skill. ```text $ bun run catalog:generate [DONE] Generated catalog: .../skills-catalog.json -[INFO] Schema: 1.3.0 +[INFO] Schema: 1.4.0 $ bun run health:check [Health Check] Summary: total=8, pass=8, warn=0, fail=0 diff --git a/docs/examples/EXAMPLE_NEW_SKILL_DIFF.zh-CN.md b/docs/examples/EXAMPLE_NEW_SKILL_DIFF.zh-CN.md index 7ebe27a..3a6f771 100644 --- a/docs/examples/EXAMPLE_NEW_SKILL_DIFF.zh-CN.md +++ b/docs/examples/EXAMPLE_NEW_SKILL_DIFF.zh-CN.md @@ -24,7 +24,17 @@ "id": "example-skill", "displayName": "Example Skill", "dependsOn": ["aelf-node-skill"], - "npm": { "name": "@example/skill", "version": "0.1.0" } + "npm": { "name": "@example/skill", "version": "0.1.0" }, + "artifacts": { "ironclawWasm": true }, + "ironclawNative": { + "runtime": "wasm-tool", + "distribution": "github-release", + "artifactUrl": "https://github.com/example/skill/releases/download/v0.1.0/example-skill.wasm", + "capabilitiesUrl": "https://github.com/example/skill/releases/download/v0.1.0/example-skill.capabilities.json", + "installCommand": "ironclaw tool install ./example-skill.wasm", + "stateModel": "isolated", + "stability": "experimental" + } } ``` @@ -33,7 +43,7 @@ ```text $ bun run catalog:generate [DONE] Generated catalog: .../skills-catalog.json -[INFO] Schema: 1.3.0 +[INFO] Schema: 1.4.0 $ bun run health:check [Health Check] Summary: total=8, pass=8, warn=0, fail=0 diff --git a/docs/schemas/skills-catalog.schema.json b/docs/schemas/skills-catalog.schema.json index ba3208e..dcb432d 100644 --- a/docs/schemas/skills-catalog.schema.json +++ b/docs/schemas/skills-catalog.schema.json @@ -8,7 +8,7 @@ "properties": { "schemaVersion": { "type": "string", - "const": "1.3.0" + "const": "1.4.0" }, "generatedAt": { "type": "string", @@ -70,11 +70,12 @@ }, "artifacts": { "type": "object", - "required": ["skillMd", "mcpServer", "openclaw"], + "required": ["skillMd", "mcpServer", "openclaw", "ironclawWasm"], "properties": { "skillMd": { "type": "boolean" }, "mcpServer": { "type": "boolean" }, - "openclaw": { "type": "boolean" } + "openclaw": { "type": "boolean" }, + "ironclawWasm": { "type": "boolean" } } }, "setupCommands": { @@ -107,6 +108,7 @@ }, "ironclaw": { "type": "string", + "description": "Schema remains backward compatible, but the current wasm-only IronClaw rollout only permits native or unsupported at runtime.", "enum": ["native", "native-setup", "manual-mcp", "manual-cli-or-mcp", "manual", "unsupported"] }, "claude_code": { @@ -136,7 +138,10 @@ "enum": ["managed-install", "package-setup", "trusted-local-install", "unsupported"] }, "installCommand": { "type": "string" }, - "requiresTrustPromotion": { "type": "boolean" } + "requiresTrustPromotion": { + "type": "boolean", + "description": "Reserved for future trust-promotion flows; not consumed in the current rollout." + } } }, "ironclaw": { @@ -152,11 +157,58 @@ "enum": ["managed-install", "package-setup", "trusted-local-install", "unsupported"] }, "installCommand": { "type": "string" }, - "requiresTrustPromotion": { "type": "boolean" } + "requiresTrustPromotion": { + "type": "boolean", + "description": "Reserved for future trust-promotion flows; not consumed in the current rollout." + } } } } }, + "ironclawNative": { + "type": "object", + "required": [ + "runtime", + "distribution", + "artifactUrl", + "capabilitiesUrl", + "installCommand", + "stateModel", + "stability" + ], + "properties": { + "runtime": { + "type": "string", + "enum": ["wasm-tool"] + }, + "distribution": { + "type": "string", + "enum": ["github-release"] + }, + "artifactUrl": { "type": "string", "format": "uri" }, + "capabilitiesUrl": { "type": "string", "format": "uri" }, + "installCommand": { "type": "string", "minLength": 1 }, + "stateModel": { + "type": "string", + "enum": ["isolated"] + }, + "stability": { + "type": "string", + "enum": ["experimental", "stable"] + } + } + }, + "clawhub": { + "type": "object", + "required": ["slug", "role"], + "properties": { + "slug": { "type": "string", "minLength": 1 }, + "role": { + "type": "string", + "enum": ["discovery-shell", "runtime-skill"] + } + } + }, "openclawToolCount": { "type": "integer", "minimum": 0 diff --git a/docs/templates/AI_NEW_SKILL_PROMPT.md b/docs/templates/AI_NEW_SKILL_PROMPT.md index 5c5316b..e3df09c 100644 --- a/docs/templates/AI_NEW_SKILL_PROMPT.md +++ b/docs/templates/AI_NEW_SKILL_PROMPT.md @@ -19,7 +19,7 @@ Follow `docs/AI_SKILL_CONTRACT.md` strictly. - Capability boundary: - MCP: - OpenClaw native: - - IronClaw native setup: + - IronClaw native wasm sidecar: - CLI: - Install-level validation allowed: @@ -32,7 +32,8 @@ Follow `docs/AI_SKILL_CONTRACT.md` strictly. - `SKILL.md` front matter name/description (and `version` + `activation.*` for IronClaw when applicable) - MCP support (`src/mcp/server.ts` or `scripts.mcp`) - `openclaw.json` if OpenClaw native is declared - - setup support (`scripts.setup` or `bin/setup.ts|bin/setup.js`), including `setup ironclaw` when IronClaw native setup is declared + - setup support (`scripts.setup` or `bin/setup.ts|bin/setup.js`) for local installers where applicable + - native IronClaw sidecar support (`ironclaw-wasm/`) when native-wasm delivery is declared - README / SKILL distribution wording: GitHub discovery only, activation via npm/ClawHub contract - Regenerate catalog and README snapshots. diff --git a/docs/templates/AI_NEW_SKILL_PROMPT.zh-CN.md b/docs/templates/AI_NEW_SKILL_PROMPT.zh-CN.md index 8ec88fd..f415f16 100644 --- a/docs/templates/AI_NEW_SKILL_PROMPT.zh-CN.md +++ b/docs/templates/AI_NEW_SKILL_PROMPT.zh-CN.md @@ -19,7 +19,7 @@ - Capability boundary: - MCP: - OpenClaw native: - - IronClaw native setup: + - IronClaw native wasm sidecar: - CLI: - Install-level validation allowed: @@ -32,7 +32,8 @@ - `SKILL.md` front matter name/description(如适用,再加 `version` 与 `activation.*`) - MCP 支持(`src/mcp/server.ts` 或 `scripts.mcp`) - 声明 OpenClaw native 时需要 `openclaw.json` - - setup 支持(`scripts.setup` 或 `bin/setup.ts|bin/setup.js`);若声明 IronClaw native setup,还需支持 `setup ironclaw` + - setup 支持(`scripts.setup` 或 `bin/setup.ts|bin/setup.js`),用于本地 installer 场景 + - 若声明 native IronClaw 交付,还需提供 `ironclaw-wasm/` sidecar - README / SKILL 明确 GitHub 只负责 discovery,activation 走 npm/ClawHub 契约 - 重新生成 catalog 与 README 快照。 diff --git a/docs/templates/NEW_SKILL_CHECKLIST.md b/docs/templates/NEW_SKILL_CHECKLIST.md index 7b5c3be..e1ddebd 100644 --- a/docs/templates/NEW_SKILL_CHECKLIST.md +++ b/docs/templates/NEW_SKILL_CHECKLIST.md @@ -18,7 +18,7 @@ Copy and check this list in your PR. - [ ] MCP support exists: `src/mcp/server.ts` or `scripts.mcp`. - [ ] If OpenClaw native is declared, `openclaw.json` exists. - [ ] If native-setup is declared, `scripts.setup` or `bin/setup.ts|bin/setup.js` exists. -- [ ] If IronClaw native setup is declared, `setup ironclaw` is supported and installs a trusted skill path. +- [ ] If IronClaw native delivery is declared, `ironclaw-wasm/` and GitHub Release artifact metadata are present. ## 3. Schema Alignment @@ -27,6 +27,7 @@ Copy and check this list in your PR. - [ ] `openclaw.json` (if present) matches `docs/schemas/openclaw.schema.json`. - [ ] Generated `skills-catalog.json` matches `docs/schemas/skills-catalog.schema.json`. - [ ] Generated catalog includes `distributionSources` and `clientInstall`. +- [ ] Generated catalog includes `artifacts.ironclawWasm` / `ironclawNative` when native IronClaw delivery exists. ## 4. Architecture and Product Requirements diff --git a/docs/templates/NEW_SKILL_CHECKLIST.zh-CN.md b/docs/templates/NEW_SKILL_CHECKLIST.zh-CN.md index eb25b83..9a4b80b 100644 --- a/docs/templates/NEW_SKILL_CHECKLIST.zh-CN.md +++ b/docs/templates/NEW_SKILL_CHECKLIST.zh-CN.md @@ -18,7 +18,7 @@ - [ ] MCP 支持存在:`src/mcp/server.ts` 或 `scripts.mcp`。 - [ ] 若声明 OpenClaw native,`openclaw.json` 存在。 - [ ] 若声明 native-setup,存在 `scripts.setup` 或 `bin/setup.ts|bin/setup.js`。 -- [ ] 若声明 IronClaw native setup,必须支持 `setup ironclaw` 且安装到 trusted skill 路径。 +- [ ] 若声明 IronClaw native 交付,必须提供 `ironclaw-wasm/` 与 GitHub Release artifact 元数据。 ## 3. Schema 一致性 @@ -27,6 +27,7 @@ - [ ] `openclaw.json`(若存在)满足 `docs/schemas/openclaw.schema.json`。 - [ ] 生成后的 `skills-catalog.json` 满足 `docs/schemas/skills-catalog.schema.json`。 - [ ] 生成后的 catalog 包含 `distributionSources` 与 `clientInstall`。 +- [ ] 若存在 native IronClaw 交付,生成后的 catalog 还包含 `artifacts.ironclawWasm` 与 `ironclawNative`。 ## 4. 架构与产品要求 diff --git a/docs/templates/SKILL_TEMPLATE.md b/docs/templates/SKILL_TEMPLATE.md index 10b4ab3..cde578a 100644 --- a/docs/templates/SKILL_TEMPLATE.md +++ b/docs/templates/SKILL_TEMPLATE.md @@ -35,7 +35,7 @@ activation: ## Distribution / Activation - GitHub repo/tree URLs are discovery-only for AI hosts. -- Preferred IronClaw activation command from npm: `bunx -p ironclaw` +- Preferred IronClaw activation is the native-wasm artifact exposed via `ironclawNative` - Preferred OpenClaw activation command from npm when managed install is unavailable: `bunx -p openclaw` ## Limits / Non-goals diff --git a/docs/templates/SKILL_TEMPLATE.zh-CN.md b/docs/templates/SKILL_TEMPLATE.zh-CN.md index ae9e9c3..eae026b 100644 --- a/docs/templates/SKILL_TEMPLATE.zh-CN.md +++ b/docs/templates/SKILL_TEMPLATE.zh-CN.md @@ -35,7 +35,7 @@ activation: ## Distribution / Activation - GitHub repo/tree URL 仅用于 discovery。 -- IronClaw 推荐 npm 激活命令:`bunx -p ironclaw` +- IronClaw 推荐通过 `ironclawNative` 暴露的 native-wasm artifact 激活 - OpenClaw 在没有 managed install 时,推荐 npm 激活命令:`bunx -p openclaw` ## Limits / Non-goals diff --git a/package.json b/package.json index 6e4e070..b64f6c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockchain-forever/aelf-skills", - "version": "0.1.3", + "version": "0.1.4", "description": "Discovery, download, and configuration hub for the aelf agent skill ecosystem.", "type": "module", "license": "MIT", diff --git a/scripts/lib/catalog.test.ts b/scripts/lib/catalog.test.ts index 72ddef0..64b396a 100644 --- a/scripts/lib/catalog.test.ts +++ b/scripts/lib/catalog.test.ts @@ -1,4 +1,6 @@ import { afterEach, describe, expect, test } from 'bun:test'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { buildCatalog } from './catalog.ts'; @@ -6,8 +8,12 @@ const PREVIOUS_SKILLS_BASE = process.env.SKILLS_BASE; const ROOT_DIR = path.resolve(import.meta.dir, '..', '..'); const FIXTURE_ROOT = path.join(ROOT_DIR, 'testdata'); const FIXTURE_WORKSPACE = path.join(FIXTURE_ROOT, 'workspace.ci.json'); +const TEMP_DIRS: string[] = []; afterEach(() => { + while (TEMP_DIRS.length > 0) { + rmSync(TEMP_DIRS.pop()!, { recursive: true, force: true }); + } if (PREVIOUS_SKILLS_BASE === undefined) { delete process.env.SKILLS_BASE; return; @@ -15,6 +21,12 @@ afterEach(() => { process.env.SKILLS_BASE = PREVIOUS_SKILLS_BASE; }); +function createTempDir(prefix: string): string { + const dir = mkdtempSync(path.join(os.tmpdir(), prefix)); + TEMP_DIRS.push(dir); + return dir; +} + describe('catalog distribution contract', () => { test('buildCatalog emits machine-readable discovery and activation fields', () => { process.env.SKILLS_BASE = FIXTURE_ROOT; @@ -33,18 +45,32 @@ describe('catalog distribution contract', () => { expect(coreSkill?.distributionSources).toEqual( expect.objectContaining({ npmPackage: '@fixture/core-skill', + clawhubId: 'fixture-core-skill', }), ); + expect(coreSkill?.artifacts.ironclawWasm).toBe(true); + expect(coreSkill?.clientSupport.ironclaw).toBe('native'); + expect(coreSkill?.clawhub).toEqual({ + slug: 'fixture-core-skill', + role: 'discovery-shell', + }); + expect(coreSkill?.ironclawNative).toEqual({ + runtime: 'wasm-tool', + distribution: 'github-release', + artifactUrl: 'https://github.com/fixture/core-skill/releases/download/v0.1.0/fixture-core-skill.wasm', + capabilitiesUrl: + 'https://github.com/fixture/core-skill/releases/download/v0.1.0/fixture-core-skill.capabilities.json', + installCommand: 'ironclaw tool install ./fixture-core-skill.wasm', + stateModel: 'isolated', + stability: 'experimental', + }); expect(coreSkill?.clientInstall.openclaw).toEqual({ - source: 'npm', - mode: 'package-setup', - installCommand: 'bunx -p @fixture/core-skill fixture-core-setup openclaw', + source: 'clawhub', + mode: 'managed-install', }); expect(coreSkill?.clientInstall.ironclaw).toEqual({ - source: 'npm', - mode: 'trusted-local-install', - installCommand: 'bunx -p @fixture/core-skill fixture-core-setup ironclaw', - requiresTrustPromotion: true, + source: 'none', + mode: 'unsupported', }); expect(nodeSkill?.distributionSources).toEqual( @@ -52,12 +78,100 @@ describe('catalog distribution contract', () => { npmPackage: '@fixture/node-skill', }), ); + expect(nodeSkill?.artifacts.ironclawWasm).toBe(false); + expect(nodeSkill?.clientSupport.ironclaw).toBe('unsupported'); expect(nodeSkill?.dependsOn).toEqual(['fixture-core-skill']); expect(nodeSkill?.clientInstall.openclaw.installCommand).toBe( 'bunx -p @fixture/node-skill fixture-node-setup openclaw', ); - expect(nodeSkill?.clientInstall.ironclaw.installCommand).toBe( - 'bunx -p @fixture/node-skill fixture-node-setup ironclaw', + expect(nodeSkill?.clientInstall.ironclaw).toEqual({ + source: 'none', + mode: 'unsupported', + }); + }); + + test('fails on dependency cycles', () => { + const tempDir = createTempDir('aelf-skills-cycle-'); + const skillADir = path.join(tempDir, 'skill-a'); + const skillBDir = path.join(tempDir, 'skill-b'); + mkdirSync(skillADir, { recursive: true }); + mkdirSync(skillBDir, { recursive: true }); + writeFileSync( + path.join(skillADir, 'package.json'), + JSON.stringify({ + name: '@fixture/skill-a', + version: '0.1.0', + repository: { url: 'https://github.com/fixture/skill-a.git' }, + }), + ); + writeFileSync(path.join(skillADir, 'SKILL.md'), '---\nname: "skill-a"\ndescription: "A"\n---\n\n# A'); + writeFileSync( + path.join(skillBDir, 'package.json'), + JSON.stringify({ + name: '@fixture/skill-b', + version: '0.1.0', + repository: { url: 'https://github.com/fixture/skill-b.git' }, + }), ); + writeFileSync(path.join(skillBDir, 'SKILL.md'), '---\nname: "skill-b"\ndescription: "B"\n---\n\n# B'); + const workspacePath = path.join(tempDir, 'workspace.json'); + writeFileSync( + workspacePath, + JSON.stringify({ + projects: [ + { path: skillADir, dependsOn: ['skill-b'] }, + { path: skillBDir, dependsOn: ['skill-a'] }, + ], + }), + ); + + expect(() => + buildCatalog({ + workspacePath, + includeLocalPaths: true, + }), + ).toThrow(/Dependency cycle detected/); + }); + + test('warns and skips ironclawNative when multiple capabilities files exist', () => { + const tempDir = createTempDir('aelf-skills-multi-caps-'); + const skillDir = path.join(tempDir, 'skill'); + const wasmDir = path.join(skillDir, 'ironclaw-wasm'); + mkdirSync(wasmDir, { recursive: true }); + writeFileSync( + path.join(skillDir, 'package.json'), + JSON.stringify({ + name: '@fixture/multi-caps', + version: '0.1.0', + repository: { url: 'https://github.com/fixture/multi-caps.git' }, + }), + ); + writeFileSync( + path.join(skillDir, 'SKILL.md'), + '---\nname: "multi-caps"\ndescription: "Multi caps"\n---\n\n# Multi Caps', + ); + writeFileSync( + path.join(wasmDir, 'Cargo.toml'), + '[package]\nname = "fixture_multi_caps"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\ncrate-type = ["cdylib"]\n', + ); + writeFileSync(path.join(wasmDir, 'first.capabilities.json'), '{"version":"0.1.0"}'); + writeFileSync(path.join(wasmDir, 'second.capabilities.json'), '{"version":"0.1.0"}'); + const workspacePath = path.join(tempDir, 'workspace.json'); + writeFileSync( + workspacePath, + JSON.stringify({ + projects: [{ path: skillDir }], + }), + ); + + const catalog = buildCatalog({ + workspacePath, + includeLocalPaths: true, + }); + const skill = catalog.skills.find(entry => entry.id === 'multi-caps'); + + expect(skill?.ironclawNative).toBeUndefined(); + expect(skill?.clientSupport.ironclaw).toBe('unsupported'); + expect(catalog.warnings.join('\n')).toContain('multiple IronClaw capabilities files found'); }); }); diff --git a/scripts/lib/catalog.ts b/scripts/lib/catalog.ts index 064619e..5a4585d 100644 --- a/scripts/lib/catalog.ts +++ b/scripts/lib/catalog.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { expandPathWithEnv, @@ -16,6 +16,7 @@ import type { SkillCatalogEntry, SkillClientInstall, SkillClientInstallEntry, + SkillIronclawNative, SkillsCatalog, WorkspaceConfig, WorkspaceProject, @@ -35,7 +36,12 @@ const DESCRIPTION_ZH_OVERRIDES: Record = { '@portkey/eoa-agent-skills': 'Portkey EOA 钱包与资产操作技能。', '@tomorrowdao/agent-skills': 'TomorrowDAO 治理、BP 与资源操作技能。', }; -const CLAWHUB_ID_OVERRIDES: Record = {}; +const CLAWHUB_ID_OVERRIDES: Record = { + '@fixture/core-skill': 'fixture-core-skill', +}; +const REPOSITORY_HOSTNAME_OVERRIDES: Record = { + 'tmrwdao.github.com': 'github.com', +}; interface BuiltSkillCandidate { entry: SkillCatalogEntry; @@ -105,7 +111,7 @@ export function buildCatalog(options: BuildCatalogOptions = {}): SkillsCatalog { skills.sort((a, b) => a.id.localeCompare(b.id)); return { - schemaVersion: '1.3.0', + schemaVersion: '1.4.0', generatedAt: new Date().toISOString(), source: path.basename(workspacePath), skills, @@ -170,6 +176,13 @@ function buildSkillEntry( const hasOpenclaw = existsSync(openclawPath); const hasCli = Boolean(pkg.scripts?.cli); const hasSetup = Boolean(setupBase); + const ironclawNative = buildIronclawNative({ + projectPath, + repositoryHttps, + packageVersion: pkg.version, + warnings, + includeLocalPaths, + }); return { id: frontMatter.name ? slugifyId(frontMatter.name) : slugifyId(pkg.name), @@ -193,6 +206,7 @@ function buildSkillEntry( skillMd: true, mcpServer: hasMcp, openclaw: hasOpenclaw, + ironclawWasm: Boolean(ironclawNative), }, setupCommands: buildSetupCommands(setupBase), clientSupport: buildClientSupport({ @@ -200,14 +214,23 @@ function buildSkillEntry( hasOpenclaw, hasCli, hasSetup, + hasIronclawNative: Boolean(ironclawNative), }), clientInstall: buildClientInstall({ packageName: pkg.name, setupBin, clawhubId, hasOpenclaw, - hasIronclaw: hasMcp && hasSetup, }), + ...(ironclawNative ? { ironclawNative } : {}), + ...(clawhubId + ? { + clawhub: { + slug: clawhubId, + role: ironclawNative ? 'discovery-shell' : 'runtime-skill', + }, + } + : {}), openclawToolCount, ...(includeLocalPaths ? { sourcePath: projectPath } : {}), }; @@ -237,6 +260,7 @@ function applyAndValidateDependencies(candidates: BuiltSkillCandidate[]): SkillC const knownIds = new Set(candidates.map(candidate => candidate.entry.id)); const skills: SkillCatalogEntry[] = []; const errors: string[] = []; + const dependencyGraph = new Map(); for (const candidate of candidates) { const { entry, declaredDependsOn } = candidate; @@ -251,9 +275,12 @@ function applyAndValidateDependencies(candidates: BuiltSkillCandidate[]): SkillC } } + dependencyGraph.set(entry.id, declaredDependsOn); skills.push(entry); } + errors.push(...detectDependencyCycles(dependencyGraph)); + if (errors.length > 0) { throw new Error(errors.join('\n')); } @@ -370,7 +397,6 @@ function buildSetupCommands(setupBase?: string) { openclaw: `${setupBase} openclaw`, cursor: `${setupBase} cursor`, claudeDesktop: `${setupBase} claude`, - ironclaw: `${setupBase} ironclaw`, }; } @@ -379,6 +405,7 @@ function buildClientSupport(input: { hasOpenclaw: boolean; hasCli: boolean; hasSetup: boolean; + hasIronclawNative: boolean; }) { const openclaw = input.hasOpenclaw ? (input.hasSetup ? 'native' : 'manual') : 'unsupported'; @@ -388,22 +415,99 @@ function buildClientSupport(input: { openclaw, cursor: input.hasMcp ? nativeMcp : 'unsupported', claude_desktop: input.hasMcp ? nativeMcp : 'unsupported', - ironclaw: input.hasMcp ? nativeMcp : 'unsupported', + ironclaw: input.hasIronclawNative ? 'native' : 'unsupported', claude_code: input.hasMcp ? 'manual-mcp' : 'unsupported', codex: input.hasMcp || input.hasCli ? 'manual-cli-or-mcp' : 'unsupported', }; } +function buildIronclawNative(input: { + projectPath: string; + repositoryHttps?: string; + packageVersion: string; + warnings: string[]; + includeLocalPaths: boolean; +}): SkillIronclawNative | undefined { + const wasmRoot = path.join(input.projectPath, 'ironclaw-wasm'); + const cargoPath = path.join(wasmRoot, 'Cargo.toml'); + if (!existsSync(cargoPath) || !input.repositoryHttps) { + return undefined; + } + + const capabilitiesFile = findIronclawCapabilitiesFile( + wasmRoot, + input.warnings, + input.projectPath, + input.includeLocalPaths, + ); + if (!capabilitiesFile) { + return undefined; + } + + warnOnIronclawVersionDrift( + cargoPath, + path.join(wasmRoot, capabilitiesFile), + input.packageVersion, + input.warnings, + input.projectPath, + input.includeLocalPaths, + ); + + const releaseBase = buildGithubReleaseBase(input.repositoryHttps, input.packageVersion); + const artifactName = capabilitiesFile.replace(/\.capabilities\.json$/, '.wasm'); + + return { + runtime: 'wasm-tool', + distribution: 'github-release', + artifactUrl: `${releaseBase}/${artifactName}`, + capabilitiesUrl: `${releaseBase}/${capabilitiesFile}`, + installCommand: `ironclaw tool install ./${artifactName}`, + stateModel: 'isolated', + stability: 'experimental', + }; +} + +function findIronclawCapabilitiesFile( + wasmRoot: string, + warnings: string[], + projectPath: string, + includeLocalPaths: boolean, +): string | undefined { + const entries = readdirSync(wasmRoot, { withFileTypes: true }); + const matches = entries + .filter(entry => entry.isFile() && entry.name.endsWith('.capabilities.json')) + .map(entry => entry.name) + .sort((a, b) => a.localeCompare(b)); + if (matches.length === 0) { + return undefined; + } + if (matches.length > 1) { + pushWarning( + warnings, + 'WARN', + projectPath, + includeLocalPaths, + `multiple IronClaw capabilities files found (${matches.join(', ')}); ironclawNative metadata skipped`, + ); + return undefined; + } + return matches[0]; +} + +function buildGithubReleaseBase(repositoryHttps: string, version: string): string { + const repoUrl = repositoryHttps.replace(/\.git$/, ''); + return `${repoUrl}/releases/download/v${version}`; +} + function buildClientInstall(input: { packageName: string; setupBin?: string; clawhubId?: string; hasOpenclaw: boolean; - hasIronclaw: boolean; }): SkillClientInstall { return { openclaw: buildOpenclawInstall(input), - ironclaw: buildIronclawInstall(input), + ironclaw: buildIronclawInstall(), }; } @@ -435,21 +539,8 @@ function buildOpenclawInstall(input: { }; } -function buildIronclawInstall(input: { - packageName: string; - setupBin?: string; - hasIronclaw: boolean; -}): SkillClientInstallEntry { - if (!input.hasIronclaw || !input.setupBin) { - return { source: 'none', mode: 'unsupported' }; - } - - return { - source: 'npm', - mode: 'trusted-local-install', - installCommand: `bunx -p ${input.packageName} ${input.setupBin} ironclaw`, - requiresTrustPromotion: true, - }; +function buildIronclawInstall(): SkillClientInstallEntry { + return { source: 'none', mode: 'unsupported' }; } function syncReadmeTable(readmePath: string, tableContent: string): void { @@ -508,11 +599,95 @@ function canonicalizeRepositoryHttps(raw?: string): string | undefined { try { const parsed = new URL(raw); - if (parsed.hostname === 'tmrwdao.github.com') { - parsed.hostname = 'github.com'; + const overrideHost = REPOSITORY_HOSTNAME_OVERRIDES[parsed.hostname]; + if (overrideHost) { + parsed.hostname = overrideHost; } return parsed.toString(); } catch { return raw; } } + +function detectDependencyCycles(graph: Map): string[] { + const errors: string[] = []; + const states = new Map(); + const stack: string[] = []; + + const visit = (id: string): void => { + const state = states.get(id); + if (state === 'visited') { + return; + } + if (state === 'visiting') { + const cycleStart = stack.indexOf(id); + const cyclePath = [...stack.slice(cycleStart), id].join(' -> '); + errors.push(`[FAIL] Dependency cycle detected: ${cyclePath}`); + return; + } + + states.set(id, 'visiting'); + stack.push(id); + for (const dependencyId of graph.get(id) || []) { + visit(dependencyId); + } + stack.pop(); + states.set(id, 'visited'); + }; + + for (const id of graph.keys()) { + visit(id); + } + + return Array.from(new Set(errors)); +} + +function warnOnIronclawVersionDrift( + cargoPath: string, + capabilitiesPath: string, + packageVersion: string, + warnings: string[], + projectPath: string, + includeLocalPaths: boolean, +): void { + const cargoContent = readFileSync(cargoPath, 'utf8'); + const cargoVersion = cargoContent.match(/^\s*version\s*=\s*"([^"]+)"/m)?.[1]; + let capabilitiesVersion: string | undefined; + + try { + capabilitiesVersion = readJsonFile<{ version?: string }>(capabilitiesPath).version; + } catch { + pushWarning( + warnings, + 'WARN', + projectPath, + includeLocalPaths, + `failed to parse IronClaw capabilities file: ${path.basename(capabilitiesPath)}`, + ); + return; + } + + const versions = [packageVersion, cargoVersion, capabilitiesVersion].filter( + (value): value is string => Boolean(value), + ); + if (versions.length < 3) { + pushWarning( + warnings, + 'WARN', + projectPath, + includeLocalPaths, + 'incomplete IronClaw version metadata; expected package.json, Cargo.toml, and capabilities.json to declare versions', + ); + return; + } + + if (new Set(versions).size > 1) { + pushWarning( + warnings, + 'WARN', + projectPath, + includeLocalPaths, + `IronClaw version drift detected (package.json=${packageVersion}, Cargo.toml=${cargoVersion}, capabilities.json=${capabilitiesVersion})`, + ); + } +} diff --git a/scripts/lib/health.test.ts b/scripts/lib/health.test.ts new file mode 100644 index 0000000..e553f3c --- /dev/null +++ b/scripts/lib/health.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from 'bun:test'; +import path from 'node:path'; +import { runHealthCheck } from './health.ts'; +import type { SkillCatalogEntry, SkillsCatalog } from './types.ts'; + +const ROOT_DIR = path.resolve(import.meta.dir, '..', '..'); +const FIXTURE_SKILL_DIR = path.join(ROOT_DIR, 'testdata', 'skills', 'fixture-core-skill'); + +function buildFixtureSkill(overrides: Partial = {}): SkillCatalogEntry { + return { + id: 'fixture-core-skill', + displayName: 'Fixture Core Skill', + npm: { + name: '@fixture/core-skill', + version: '0.1.0', + }, + repository: { + https: 'https://github.com/fixture/core-skill.git', + }, + distributionSources: { + githubRepo: 'https://github.com/fixture/core-skill.git', + npmPackage: '@fixture/core-skill', + clawhubId: 'fixture-core-skill', + }, + description: 'Fixture core skill for tests.', + capabilities: ['fixture'], + artifacts: { + skillMd: true, + mcpServer: true, + openclaw: true, + ironclawWasm: true, + }, + setupCommands: { + install: 'bun install', + openclaw: 'bun run setup openclaw', + }, + clientSupport: { + openclaw: 'native', + cursor: 'native-setup', + claude_desktop: 'native-setup', + ironclaw: 'native', + claude_code: 'manual-mcp', + codex: 'manual-cli-or-mcp', + }, + clientInstall: { + openclaw: { + source: 'clawhub', + mode: 'managed-install', + }, + ironclaw: { + source: 'none', + mode: 'unsupported', + }, + }, + ironclawNative: { + runtime: 'wasm-tool', + distribution: 'github-release', + artifactUrl: 'https://github.com/fixture/core-skill/releases/download/v0.1.0/fixture-core-skill.wasm', + capabilitiesUrl: + 'https://github.com/fixture/core-skill/releases/download/v0.1.0/fixture-core-skill.capabilities.json', + installCommand: 'ironclaw tool install ./fixture-core-skill.wasm', + stateModel: 'isolated', + stability: 'experimental', + }, + clawhub: { + slug: 'fixture-core-skill', + role: 'discovery-shell', + }, + openclawToolCount: 1, + sourcePath: FIXTURE_SKILL_DIR, + ...overrides, + }; +} + +function buildCatalog(skills: SkillCatalogEntry[]): SkillsCatalog { + return { + schemaVersion: '1.4.0', + generatedAt: '2026-03-10T00:00:00.000Z', + source: 'test', + skills, + warnings: [], + }; +} + +describe('health severity gate', () => { + test('treats illegal ironclaw support levels as fail', () => { + const report = runHealthCheck( + buildCatalog([ + buildFixtureSkill({ + clientSupport: { + openclaw: 'native', + cursor: 'native-setup', + claude_desktop: 'native-setup', + ironclaw: 'manual', + claude_code: 'manual-mcp', + codex: 'manual-cli-or-mcp', + }, + artifacts: { + skillMd: true, + mcpServer: true, + openclaw: true, + ironclawWasm: false, + }, + ironclawNative: undefined, + }), + ]), + ); + + expect(report.results[0].status).toBe('fail'); + expect(report.results[0].issues).toContainEqual({ + message: 'ironclaw support must be native or unsupported in wasm-only rollout', + severity: 'fail', + }); + }); +}); diff --git a/scripts/lib/health.ts b/scripts/lib/health.ts index 21b8d8f..8582734 100644 --- a/scripts/lib/health.ts +++ b/scripts/lib/health.ts @@ -8,17 +8,23 @@ interface PackageJsonLike { bin?: string | Record; } +export interface HealthIssue { + message: string; + severity: 'warn' | 'fail'; +} + export interface SkillHealthResult { id: string; skillDir: string; status: 'pass' | 'warn' | 'fail'; - issues: string[]; + issues: HealthIssue[]; checks: { packageJson: boolean; setupScript: boolean; setupBin: boolean; mcpServer: boolean; openclawJson: boolean; + ironclawWasm: boolean; cliScript: boolean; }; } @@ -77,12 +83,12 @@ export function printHealthReport(report: HealthCheckReport): void { } console.log('\n[Health Check] Skill matrix'); - console.log('| Skill ID | Status | package.json | setup | setup-bin | MCP | OpenClaw | CLI |'); - console.log('|---|---|---:|---:|---:|---:|---:|---:|'); + console.log('| Skill ID | Status | package.json | setup | setup-bin | MCP | OpenClaw | IronClaw WASM | CLI |'); + console.log('|---|---|---:|---:|---:|---:|---:|---:|---:|'); for (const row of report.results) { console.log( - `| ${row.id} | ${row.status.toUpperCase()} | ${toFlag(row.checks.packageJson)} | ${toFlag(row.checks.setupScript)} | ${toFlag(row.checks.setupBin)} | ${toFlag(row.checks.mcpServer)} | ${toFlag(row.checks.openclawJson)} | ${toFlag(row.checks.cliScript)} |`, + `| ${row.id} | ${row.status.toUpperCase()} | ${toFlag(row.checks.packageJson)} | ${toFlag(row.checks.setupScript)} | ${toFlag(row.checks.setupBin)} | ${toFlag(row.checks.mcpServer)} | ${toFlag(row.checks.openclawJson)} | ${toFlag(row.checks.ironclawWasm)} | ${toFlag(row.checks.cliScript)} |`, ); } @@ -97,7 +103,7 @@ export function printHealthReport(report: HealthCheckReport): void { for (const row of report.results) { if (row.issues.length === 0) continue; for (const issue of row.issues) { - console.log(` - ${row.id}: ${issue}`); + console.log(` - ${row.id} [${issue.severity.toUpperCase()}]: ${issue.message}`); } } } @@ -109,13 +115,19 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu id: skill.id, skillDir: '(unresolved)', status: 'fail', - issues: ['local source path missing; provide --skills-root or use local-path catalog mode'], + issues: [ + { + message: 'local source path missing; provide --skills-root or use local-path catalog mode', + severity: 'fail', + }, + ], checks: { packageJson: false, setupScript: false, setupBin: false, mcpServer: false, openclawJson: false, + ironclawWasm: false, cliScript: false, }, }; @@ -124,6 +136,7 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu const packagePath = path.join(skillDir, 'package.json'); const mcpPath = path.join(skillDir, 'src', 'mcp', 'server.ts'); const openclawPath = path.join(skillDir, 'openclaw.json'); + const ironclawWasmPath = path.join(skillDir, 'ironclaw-wasm', 'Cargo.toml'); const checks = { packageJson: existsSync(packagePath), @@ -131,14 +144,15 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu setupBin: false, mcpServer: existsSync(mcpPath), openclawJson: existsSync(openclawPath), + ironclawWasm: existsSync(ironclawWasmPath), cliScript: false, }; - const issues: string[] = []; + const issues: HealthIssue[] = []; let pkgScripts: Record = {}; if (!checks.packageJson) { - issues.push('package.json missing'); + issues.push({ message: 'package.json missing', severity: 'fail' }); } else { try { const pkg = readJsonFile(packagePath); @@ -151,7 +165,7 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu checks.mcpServer = checks.mcpServer || Boolean(pkgScripts.mcp); checks.cliScript = Boolean(pkgScripts.cli); } catch { - issues.push('package.json parse failed'); + issues.push({ message: 'package.json parse failed', severity: 'fail' }); } } @@ -159,11 +173,7 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu let status: SkillHealthResult['status'] = 'pass'; if (issues.length > 0) { - status = issues.some( - issue => issue.includes('missing') || issue.includes('not executable'), - ) - ? 'fail' - : 'warn'; + status = issues.some(issue => issue.severity === 'fail') ? 'fail' : 'warn'; } return { @@ -178,63 +188,128 @@ function checkSkill(skill: SkillCatalogEntry, skillDir: string): SkillHealthResu function validateExpectedSupport( skill: SkillCatalogEntry, checks: SkillHealthResult['checks'], - issues: string[], + issues: HealthIssue[], ): void { if (skill.clientSupport.openclaw === 'native' && !checks.openclawJson) { - issues.push('declared openclaw native but openclaw.json missing'); + issues.push({ message: 'declared openclaw native but openclaw.json missing', severity: 'fail' }); } const needsSetup = skill.clientSupport.cursor === 'native-setup' || - skill.clientSupport.claude_desktop === 'native-setup' || - skill.clientSupport.ironclaw === 'native-setup'; + skill.clientSupport.claude_desktop === 'native-setup'; if (needsSetup && !checks.setupScript) { - issues.push('declared native-setup but setup command not available'); + issues.push({ message: 'declared native-setup but setup command not available', severity: 'fail' }); } if (needsSetup && !checks.setupBin) { - issues.push('declared native-setup but npm installer bin is missing'); + issues.push({ message: 'declared native-setup but npm installer bin is missing', severity: 'fail' }); } const needsMcp = skill.clientSupport.cursor !== 'unsupported' || skill.clientSupport.claude_desktop !== 'unsupported' || - skill.clientSupport.ironclaw !== 'unsupported' || skill.clientSupport.claude_code !== 'unsupported'; if (needsMcp && !checks.mcpServer) { - issues.push('MCP support declared but src/mcp/server.ts missing'); + issues.push({ message: 'MCP support declared but src/mcp/server.ts missing', severity: 'fail' }); } const needsCli = skill.clientSupport.codex === 'manual-cli-or-mcp'; if (needsCli && !checks.mcpServer && !checks.cliScript) { - issues.push('codex support declared but neither MCP nor CLI script is available'); + issues.push({ + message: 'codex support declared but neither MCP nor CLI script is available', + severity: 'fail', + }); + } + + if ( + skill.clientSupport.ironclaw !== 'native' && + skill.clientSupport.ironclaw !== 'unsupported' + ) { + issues.push({ + message: 'ironclaw support must be native or unsupported in wasm-only rollout', + severity: 'fail', + }); } - if (skill.clientSupport.ironclaw === 'native-setup') { - if (skill.clientInstall.ironclaw.mode !== 'trusted-local-install') { - issues.push('ironclaw native-setup declared but clientInstall.ironclaw is not executable'); + if (skill.clientSupport.ironclaw === 'native') { + if (!skill.artifacts.ironclawWasm) { + issues.push({ + message: 'ironclaw native declared but artifacts.ironclawWasm is false', + severity: 'fail', + }); + } + if (!checks.ironclawWasm) { + issues.push({ + message: 'ironclaw native declared but ironclaw-wasm/Cargo.toml missing', + severity: 'fail', + }); } - if (!skill.distributionSources.npmPackage) { - issues.push('ironclaw native-setup declared but distributionSources.npmPackage missing'); + if (!skill.ironclawNative) { + issues.push({ + message: 'ironclaw native declared but ironclawNative contract missing', + severity: 'fail', + }); + } else { + if (!skill.ironclawNative.artifactUrl) { + issues.push({ message: 'ironclaw native declared but artifactUrl missing', severity: 'fail' }); + } + if (!skill.ironclawNative.capabilitiesUrl) { + issues.push({ + message: 'ironclaw native declared but capabilitiesUrl missing', + severity: 'fail', + }); + } + if (!skill.ironclawNative.installCommand) { + issues.push({ + message: 'ironclaw native declared but installCommand missing', + severity: 'fail', + }); + } } - if (!skill.clientInstall.ironclaw.installCommand) { - issues.push('ironclaw native-setup declared but installCommand missing'); + + if (skill.clientInstall.ironclaw.mode !== 'unsupported') { + issues.push({ + message: 'ironclaw native declared but clientInstall.ironclaw must stay unsupported', + severity: 'fail', + }); } } + if ( + skill.clientSupport.ironclaw === 'unsupported' && + skill.clientInstall.ironclaw.mode !== 'unsupported' + ) { + issues.push({ + message: 'ironclaw unsupported declared but clientInstall.ironclaw is still executable', + severity: 'fail', + }); + } + if (skill.clientSupport.openclaw === 'native') { if (skill.clientInstall.openclaw.mode === 'managed-install') { if (!skill.distributionSources.clawhubId) { - issues.push('openclaw managed-install declared but distributionSources.clawhubId missing'); + issues.push({ + message: 'openclaw managed-install declared but distributionSources.clawhubId missing', + severity: 'fail', + }); } } else if (skill.clientInstall.openclaw.mode === 'package-setup') { if (!skill.distributionSources.npmPackage) { - issues.push('openclaw package-setup declared but distributionSources.npmPackage missing'); + issues.push({ + message: 'openclaw package-setup declared but distributionSources.npmPackage missing', + severity: 'fail', + }); } if (!skill.clientInstall.openclaw.installCommand) { - issues.push('openclaw package-setup declared but installCommand missing'); + issues.push({ + message: 'openclaw package-setup declared but installCommand missing', + severity: 'fail', + }); } } else { - issues.push('openclaw native support declared but clientInstall.openclaw is not executable'); + issues.push({ + message: 'openclaw native support declared but clientInstall.openclaw is not executable', + severity: 'fail', + }); } } } diff --git a/scripts/lib/types.ts b/scripts/lib/types.ts index bceea58..c35246f 100644 --- a/scripts/lib/types.ts +++ b/scripts/lib/types.ts @@ -33,6 +33,7 @@ export interface SkillClientInstallEntry { source: ClientInstallSource; mode: ClientInstallMode; installCommand?: string; + /** Reserved for future trust-promotion flows; current rollout does not consume this field. */ requiresTrustPromotion?: boolean; } @@ -45,6 +46,22 @@ export interface SkillArtifacts { skillMd: boolean; mcpServer: boolean; openclaw: boolean; + ironclawWasm: boolean; +} + +export interface SkillIronclawNative { + runtime: 'wasm-tool'; + distribution: 'github-release'; + artifactUrl: string; + capabilitiesUrl: string; + installCommand: string; + stateModel: 'isolated'; + stability: 'experimental' | 'stable'; +} + +export interface SkillClawhub { + slug: string; + role: 'discovery-shell' | 'runtime-skill'; } export interface SkillClientSupport { @@ -78,13 +95,15 @@ export interface SkillCatalogEntry { setupCommands: SkillSetupCommands; clientSupport: SkillClientSupport; clientInstall: SkillClientInstall; + ironclawNative?: SkillIronclawNative; + clawhub?: SkillClawhub; openclawToolCount: number; dependsOn?: string[]; sourcePath?: string; } export interface SkillsCatalog { - schemaVersion: '1.3.0'; + schemaVersion: '1.4.0'; generatedAt: string; source: string; skills: SkillCatalogEntry[]; diff --git a/scripts/lib/update-check.test.ts b/scripts/lib/update-check.test.ts index 8e5589b..494ba54 100644 --- a/scripts/lib/update-check.test.ts +++ b/scripts/lib/update-check.test.ts @@ -90,7 +90,7 @@ describe('update-check live flow', () => { catalogPath, JSON.stringify( { - schemaVersion: '1.3.0', + schemaVersion: '1.4.0', generatedAt: '2026-03-05T00:00:00.000Z', source: 'test', warnings: [], @@ -119,7 +119,7 @@ describe('update-check live flow', () => { openclaw: 'native', cursor: 'native-setup', claude_desktop: 'native-setup', - ironclaw: 'native-setup', + ironclaw: 'unsupported', claude_code: 'manual', codex: 'manual', }, @@ -251,7 +251,7 @@ describe('update-check live flow', () => { catalogPath, JSON.stringify( { - schemaVersion: '1.3.0', + schemaVersion: '1.4.0', generatedAt: '2026-03-05T00:00:00.000Z', source: 'test', warnings: [], @@ -269,7 +269,7 @@ describe('update-check live flow', () => { openclaw: 'native', cursor: 'native-setup', claude_desktop: 'native-setup', - ironclaw: 'native-setup', + ironclaw: 'unsupported', claude_code: 'manual', codex: 'manual', }, @@ -288,7 +288,7 @@ describe('update-check live flow', () => { openclaw: 'native', cursor: 'native-setup', claude_desktop: 'native-setup', - ironclaw: 'native-setup', + ironclaw: 'unsupported', claude_code: 'manual', codex: 'manual', }, diff --git a/scripts/security-audit.test.ts b/scripts/security-audit.test.ts new file mode 100644 index 0000000..15cd3ab --- /dev/null +++ b/scripts/security-audit.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from 'bun:test'; +import { collectIssues } from './security-audit.ts'; +import type { SkillsCatalog } from './lib/types.ts'; + +function buildCatalog(command: string): SkillsCatalog { + return { + schemaVersion: '1.4.0', + generatedAt: '2026-03-10T00:00:00.000Z', + source: 'test', + warnings: [], + skills: [ + { + id: 'fixture', + displayName: 'Fixture', + npm: { name: '@fixture/skill', version: '0.1.0' }, + repository: { https: 'https://github.com/fixture/skill.git' }, + distributionSources: { npmPackage: '@fixture/skill' }, + description: 'fixture', + capabilities: [], + artifacts: { + skillMd: true, + mcpServer: true, + openclaw: false, + ironclawWasm: false, + }, + setupCommands: { + install: command, + }, + clientSupport: { + openclaw: 'unsupported', + cursor: 'manual-mcp', + claude_desktop: 'manual-mcp', + ironclaw: 'unsupported', + claude_code: 'manual-mcp', + codex: 'manual-cli-or-mcp', + }, + clientInstall: { + openclaw: { source: 'none', mode: 'unsupported' }, + ironclaw: { source: 'none', mode: 'unsupported' }, + }, + openclawToolCount: 0, + }, + ], + }; +} + +describe('security-audit risk patterns', () => { + test('detects && command chaining', () => { + const issues = collectIssues(buildCatalog('bun install && bun run setup')); + expect(issues).toHaveLength(1); + expect(issues[0].rule).toBe('multi-command chain with &&'); + }); + + test('detects eval execution', () => { + const issues = collectIssues(buildCatalog('eval ./script.sh')); + expect(issues).toHaveLength(1); + expect(issues[0].rule).toBe('eval execution'); + }); +}); diff --git a/scripts/security-audit.ts b/scripts/security-audit.ts index ae34590..c8df620 100644 --- a/scripts/security-audit.ts +++ b/scripts/security-audit.ts @@ -20,6 +20,8 @@ const RISK_PATTERNS: Array<{ name: string; regex: RegExp }> = [ { name: 'command substitution using $(...)', regex: /\$\([^\n)]*\)/ }, { name: 'command substitution using backticks', regex: /`[^`]+`/ }, { name: 'multi-command chain with semicolon', regex: /;\s*\S+/ }, + { name: 'multi-command chain with &&', regex: /&&\s*\S+/ }, + { name: 'eval execution', regex: /\beval\b/i }, ]; function parseArgs(): CliOptions { @@ -37,7 +39,7 @@ function parseArgs(): CliOptions { return { catalogPath }; } -function collectIssues(catalog: SkillsCatalog): SecurityIssue[] { +export function collectIssues(catalog: SkillsCatalog): SecurityIssue[] { const issues: SecurityIssue[] = []; for (const skill of catalog.skills) { @@ -73,6 +75,20 @@ function collectIssues(catalog: SkillsCatalog): SecurityIssue[] { } } } + + if (skill.ironclawNative?.installCommand) { + for (const pattern of RISK_PATTERNS) { + if (pattern.regex.test(skill.ironclawNative.installCommand)) { + issues.push({ + skillId: skill.id, + field: 'ironclawNative.installCommand', + command: skill.ironclawNative.installCommand, + rule: pattern.name, + }); + break; + } + } + } } return issues; @@ -105,4 +121,6 @@ function main(): void { } } -main(); +if (import.meta.main) { + main(); +} diff --git a/skills-catalog.json b/skills-catalog.json index c952b2d..658a663 100644 --- a/skills-catalog.json +++ b/skills-catalog.json @@ -1,6 +1,6 @@ { - "schemaVersion": "1.3.0", - "generatedAt": "2026-03-09T14:24:24.785Z", + "schemaVersion": "1.4.0", + "generatedAt": "2026-03-30T03:54:25.207Z", "source": "workspace.json", "skills": [ { @@ -29,21 +29,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -54,10 +54,8 @@ "installCommand": "bunx -p @blockchain-forever/aelf-node-skill aelf-node-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @blockchain-forever/aelf-node-skill aelf-node-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, "openclawToolCount": 11 @@ -87,21 +85,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -112,10 +110,8 @@ "installCommand": "bunx -p @aelfscan/agent-skills aelfscan-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @aelfscan/agent-skills aelfscan-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, "openclawToolCount": 61 @@ -146,21 +142,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -171,10 +167,8 @@ "installCommand": "bunx -p @awaken-finance/agent-kit awaken-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @awaken-finance/agent-kit awaken-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, "openclawToolCount": 11 @@ -205,21 +199,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -230,10 +224,8 @@ "installCommand": "bunx -p @eforest-finance/agent-skills eforest-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @eforest-finance/agent-skills eforest-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, "openclawToolCount": 48 @@ -243,7 +235,7 @@ "displayName": "Portkey CA Agent Skill", "npm": { "name": "@portkey/ca-agent-skills", - "version": "2.0.0" + "version": "2.3.0" }, "repository": { "https": "https://github.com/Portkey-Wallet/ca-agent-skills.git" @@ -256,7 +248,7 @@ "description_zh": "Portkey CA 钱包注册、认证、Guardian 与转账技能。", "capabilities": [ "Auth operations: verifier, email code, register, recover, status", - "Query operations: account, guardian, assets, chain config", + "Query operations: account, guardian, assets, chain config, transfer preflight", "Tx operations: transfer, contract call, approvals, keystore workflows", "Shared wallet context: auto-set active CA profile for cross-skill signer resolution", "Supports SDK, CLI, MCP, OpenClaw, and IronClaw integration from one codebase." @@ -264,21 +256,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -289,20 +281,18 @@ "installCommand": "bunx -p @portkey/ca-agent-skills portkey-ca-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @portkey/ca-agent-skills portkey-ca-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, - "openclawToolCount": 28 + "openclawToolCount": 32 }, { "id": "portkey-eoa-agent-skills", "displayName": "Portkey EOA Agent Skill", "npm": { "name": "@portkey/eoa-agent-skills", - "version": "1.2.4" + "version": "1.2.6" }, "repository": { "https": "https://github.com/Portkey-Wallet/eoa-agent-skills.git" @@ -323,21 +313,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": true }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "native", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -348,12 +338,19 @@ "installCommand": "bunx -p @portkey/eoa-agent-skills portkey-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @portkey/eoa-agent-skills portkey-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, + "ironclawNative": { + "runtime": "wasm-tool", + "distribution": "github-release", + "artifactUrl": "https://github.com/Portkey-Wallet/eoa-agent-skills/releases/download/v1.2.6/portkey-eoa-ironclaw.wasm", + "capabilitiesUrl": "https://github.com/Portkey-Wallet/eoa-agent-skills/releases/download/v1.2.6/portkey-eoa-ironclaw.capabilities.json", + "installCommand": "ironclaw tool install ./portkey-eoa-ironclaw.wasm", + "stateModel": "isolated", + "stability": "experimental" + }, "openclawToolCount": 21 }, { @@ -361,7 +358,7 @@ "displayName": "TomorrowDAO Agent Skill", "npm": { "name": "@tomorrowdao/agent-skills", - "version": "0.1.4" + "version": "0.2.0" }, "repository": { "https": "https://github.com/TomorrowDAOProject/tomorrowDAO-skill.git" @@ -374,6 +371,8 @@ "description_zh": "TomorrowDAO 治理、BP 与资源操作技能。", "capabilities": [ "DAO domain: create/update/proposal/discussion operations", + "Token read helpers: allowance view, balance view", + "DAO read helpers: proposal my-info, DAO token allowance view alias, DAO token balance view alias", "Network governance and BP election operation set", "Resource token trading with unified ToolResult/TxReceipt outputs", "Shared signer resolution for send mode: `explicit -> context -> env`", @@ -382,21 +381,21 @@ "artifacts": { "skillMd": true, "mcpServer": true, - "openclaw": true + "openclaw": true, + "ironclawWasm": false }, "setupCommands": { "install": "bun install", "list": "bun run setup list", "openclaw": "bun run setup openclaw", "cursor": "bun run setup cursor", - "claudeDesktop": "bun run setup claude", - "ironclaw": "bun run setup ironclaw" + "claudeDesktop": "bun run setup claude" }, "clientSupport": { "openclaw": "native", "cursor": "native-setup", "claude_desktop": "native-setup", - "ironclaw": "native-setup", + "ironclaw": "unsupported", "claude_code": "manual-mcp", "codex": "manual-cli-or-mcp" }, @@ -407,17 +406,12 @@ "installCommand": "bunx -p @tomorrowdao/agent-skills tomorrowdao-setup openclaw" }, "ironclaw": { - "source": "npm", - "mode": "trusted-local-install", - "installCommand": "bunx -p @tomorrowdao/agent-skills tomorrowdao-setup ironclaw", - "requiresTrustPromotion": true + "source": "none", + "mode": "unsupported" } }, - "openclawToolCount": 41 + "openclawToolCount": 44 } ], - "warnings": [ - "[WARN] portkey/agent-skills-e2e: SKILL.md not found, project skipped", - "[WARN] vibeCoding/aelf-skill-prompt-pilot: SKILL.md not found, project skipped" - ] + "warnings": [] } diff --git a/testdata/skills/fixture-core-skill/ironclaw-wasm/Cargo.toml b/testdata/skills/fixture-core-skill/ironclaw-wasm/Cargo.toml new file mode 100644 index 0000000..fc84233 --- /dev/null +++ b/testdata/skills/fixture-core-skill/ironclaw-wasm/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fixture_core_ironclaw" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] diff --git a/testdata/skills/fixture-core-skill/ironclaw-wasm/fixture-core-skill.capabilities.json b/testdata/skills/fixture-core-skill/ironclaw-wasm/fixture-core-skill.capabilities.json new file mode 100644 index 0000000..debf8f4 --- /dev/null +++ b/testdata/skills/fixture-core-skill/ironclaw-wasm/fixture-core-skill.capabilities.json @@ -0,0 +1,11 @@ +{ + "version": "0.1.0", + "wit_version": "0.3.0", + "capabilities": { + "workspace": { + "allowed_prefixes": [ + "fixture-core/" + ] + } + } +} diff --git a/testdata/skills/fixture-core-skill/package.json b/testdata/skills/fixture-core-skill/package.json index 2d8e627..cbca871 100644 --- a/testdata/skills/fixture-core-skill/package.json +++ b/testdata/skills/fixture-core-skill/package.json @@ -2,6 +2,10 @@ "name": "@fixture/core-skill", "version": "0.1.0", "description": "Fixture core skill for CI gates.", + "repository": { + "type": "git", + "url": "https://github.com/fixture/core-skill.git" + }, "bin": { "fixture-core-setup": "./bin/setup.js" },