Claude Skill

skill-authoring

创建、设计、修改、升级、重构和验证 AgentDock Skill 时使用;负责可移植核心、文档、引用、辅助脚本、测试、版本和本地安装验证。

LLM Mart · 0 points · 8 views 17 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download uvwt-agentdock-core-skills_skill-authoring-f0589bf.zip · 14 KB
uvwt/agentdock 1005 121 forks Apache-2.0 Updated 10d ago
Part of uvwt/agentdock — 2 skills

Install

skills CLI npx skills add https://github.com/uvwt/agentdock/tree/main/core-skills/skill-authoring
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install uvwt-agentdock@llmmart
Git git clone https://github.com/uvwt/agentdock.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole uvwt/agentdock collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Skill Authoring

用于创建或维护 AgentDock 第一方 Skill。Skill 的本体是模型可读取的说明文档;工具负责真实检查、编辑、命令执行、打包、安装和验证。

目标 Skill 应由两部分构成:

可移植核心契约
+
可选的宿主适配说明

移除 AgentDock 专属适配说明后,Skill 的业务流程、包内引用、环境变量契约和辅助脚本仍应完整可用。

何时使用

使用本 Skill 处理:

  • 创建新的第一方 Skill;
  • 修改、重构或升级现有 Skill;
  • 调整触发描述、正文流程、引用资料或辅助脚本;
  • 补充测试、示例、安全约束和可移植性检查;
  • 递增版本并完成源码侧与当前激活版本验证。

不要使用本 Skill 处理第三方 Skill 的正式安全审查、真实凭据配置或已安装版本回滚。这些属于 skill-installation。

核心原则

  1. 先定义模型何时应该选择该 Skill,再写正文。
  2. Skill 只描述方法、边界和工具选择,不承担统一执行职责。
  3. Skill 核心契约必须与宿主无关;包内文件使用相对路径,环境由运行宿主注入。
  4. 简单 Skill 优先只有一份 SKILL.md;只有确有需要时才增加引用、脚本或测试。
  5. 修改正文、引用、脚本或行为后必须递增语义化版本。
  6. 同名同版本内容必须保持不可变。
  7. 环境值、设备状态和运行数据不得进入 Skill 包。
  8. 所有验证都要落到当前已安装并激活的版本,不能只看源码目录。
  9. 第一方 Skill 必须通过本 Skill 的 lint,不能只通过包安装校验。

完整规范见包内 references/skill-package-spec.md。

标准流程

1. 理解需求和触发条件

先明确:

  • 用户真正要解决的问题;
  • 模型在什么请求下应选择该 Skill;
  • 哪些相邻任务不属于该 Skill;
  • 需要调用哪些真实工具;
  • 是否需要辅助脚本、引用资料或测试;
  • 是否涉及网络、写入、删除、凭据或高风险动作。

不要用“管理某能力全生命周期”这类宽泛描述。description 必须让模型能稳定判断何时选中它。

2. 确定职责边界

正文至少说明:

  • 适用场景和不适用场景;
  • 读取或修改的对象;
  • 默认只读行为;
  • 写操作和破坏性操作的确认规则;
  • 失败时需要返回的证据。

一个 Skill 应围绕一个稳定能力边界组织。需求已经跨越独立职责时,应拆成多个 Skill。

3. 创建源码目录

普通第一方和社区 Skill 默认放在独立的 agentdock-skills 仓库:

skills/<skill-name>/

只有随 AgentDock 安装包自举、与运行时版本强绑定的核心 Skill 才放在 AgentDock 主仓库:

core-skills/<skill-name>/

按需选择结构:

skills/<skill-name>/
└── SKILL.md
skills/<skill-name>/
├── SKILL.md
├── references/
├── scripts/
└── tests/
skills/<skill-name>/
├── SKILL.md
├── run.py
└── tests/

不要为了形式创建空目录,也不要把普通集成重新放回 AgentDock 主仓库。

4. 编写 Frontmatter

当前 AgentDock 正式解析:

---
name: example-skill
description: 清楚说明何时使用、解决什么问题
version: 1.0.0
---

要求:

  • name 使用稳定、简短、全小写的连字符名称;
  • description 同时覆盖触发场景和能力边界;
  • version 使用语义化版本;
  • Frontmatter 后必须有非空 Markdown 正文;
  • 不增加当前解析器未支持的环境变量或执行字段。

5. 编写可移植核心

目标 Skill 的正文和脚本默认只假设:

  • 当前工作目录是 Skill 包根目录;
  • 包内资源可通过相对路径访问;
  • 环境变量来自当前进程环境;
  • 运行宿主负责选择工具、切换目录和注入环境;
  • 不依赖 AgentDock 的安装目录、状态目录或专属变量。

有根目录脚本时,通用执行示例应写成:

printf '%s' '{"skill_action":"status"}' | python3 run.py

不得把以下内容作为核心运行前提:

  • ~/.agentdock/skill-store/installed/...;
  • 固定安装版本号;
  • AGENTDOCK_DIR、AGENTDOCK_HOME 或 AGENTDOCK_SKILL_DIR 用于定位包内脚本或私有数据;
  • skill_env、exec_command 或 skill://;
  • 固定用户绝对路径;
  • 主动读取或 source AgentDock 私有环境文件。

AgentDock 工具调用可以出现在单独的“AgentDock 适配/验证”说明中,但删除该部分后,Skill 仍必须可用。

6. 声明环境变量

每个需要配置的目标 Skill 都必须在正文中明确声明变量:

## 环境变量

| 变量 | 类型 | 必填 | 说明 |
|---|---|---:|---|
| EXAMPLE_BASE_URL | config | 是 | 服务地址 |
| EXAMPLE_API_KEY | secret | 是 | API Key |

类型至少区分 config 和 secret,并说明缺失变量时哪些能力不可用。

目标 Skill 只声明变量名和用途,不声明 AgentDock 私有保存路径,不保存真实值。辅助脚本只从当前进程环境读取变量。

在 AgentDock 本地验证时,环境值由 skill_package env_set/env_unset/env_list 管理,并由 exec_command 的 skill 上下文注入本次子进程。环境不会写入 AgentDock 主进程或系统环境。

7. 编写引用资料

适合放入 references/ 的内容包括:

  • 稳定协议说明;
  • 较长检查清单;
  • API 字段或错误码表;
  • 不需要每次完整加载的背景资料。

目标 SKILL.md 使用包内相对路径,例如:

references/api.md

不要把 skill://<name>/... 写成核心引用契约,也不要依赖另一个 Skill 的安装目录。AgentDock 在验证当前激活包时可通过 read_file skill://<name>/... 读取资源。

8. 编写辅助脚本

辅助脚本只是可选资源,不是 Skill 本体。需要脚本时:

  • 输入使用 stdin JSON 对象;
  • 顶层动作字段统一使用 skill_action;
  • 密钥只从当前进程环境读取;
  • 不通过命令行参数传递秘密;
  • 输出结构化 JSON;
  • 错误返回稳定 code 和可读 message;
  • 状态检查优先只读;
  • 写操作遵守用户确认规则;
  • 日志和错误不得回显秘密;
  • 包内文件使用相对路径或基于脚本自身目录定位。

推荐输入:

{
  "skill_action": "status"
}

通用执行在 Skill 包根目录运行:

python3 run.py

AgentDock 验证时使用 exec_command 的 skill: "<skill-name>" 绑定当前激活目录与独立环境,不手工解析版本目录。

9. 运行可移植性 lint

本 Skill 的 run.py 提供:

  • status:报告 lint 版本和规则数量;
  • lint:检查目标 Skill 的可移植核心和宿主绑定问题。

输入示例:

{
  "skill_action": "lint",
  "source": "/path/to/agentdock-skills/skills/example-skill"
}

结果包含:

  • portable;
  • error_count;
  • warning_count;
  • 每条 issue 的 code、severity、文件、行号、说明和修复建议。

硬编码已安装版本目录、依赖 AgentDock 专属目录变量、主动读取 AgentDock 环境文件和固定用户绝对路径属于 error。AgentDock 专属工具或 URI 出现在目标 SKILL.md 中属于 warning,需要确认它们只存在于可选适配说明。

对已安装目录运行 lint 时,会忽略包根目录下由 AgentDock 安装器生成的 .agentdock-install.json。该文件属于宿主安装回执,不是 Skill 包内容;同名文件出现在包内其他目录时仍会正常扫描。

在 AgentDock 中,安装新版 skill-authoring 后可这样运行:

exec_command
  skill: skill-authoring
  cmd: python3 run.py
  stdin: {"skill_action":"lint","source":"/path/to/source"}

创建或修改第一方 Skill 时,portable 必须为 true;warning 必须逐项修复或说明为什么属于可选宿主适配。

10. 编写测试

测试覆盖真实风险,至少考虑:

  • Frontmatter 和正文可解析;
  • 输入不是 JSON 对象时明确失败;
  • 未知 skill_action 明确失败;
  • 缺失环境变量时只报告变量名,不泄露值;
  • 只读状态检查不产生写入;
  • 破坏性动作缺少确认时拒绝;
  • 日志和错误不包含秘密;
  • 平台或依赖缺失时返回可诊断信息;
  • 包从根目录使用相对命令运行;
  • 脚本不依赖 AgentDock 私有目录。

11. 安全和质量检查

提交前检查:

  • 包内没有真实密码、Token、Cookie、认证缓存或会话文件;
  • 没有 .env、缓存、数据库、截图、下载文件和运行结果;
  • 没有 __pycache__、*.pyc、node_modules 或编译产物;
  • 没有固定用户绝对路径、符号链接或路径逃逸;
  • 没有旧式统一执行协议、旧式环境工具示例或旧清单;
  • 网络目标、文件写入、删除、上传和权限变化均被明确说明;
  • 依赖安装不会被隐藏执行。

明确禁止生成或恢复:

  • agentdock.yaml;
  • skill_run;
  • skill_env_manage;
  • AGENTDOCK_OPERATION;
  • PLUGIN_* 旧协议;
  • 旧式 operation 或 entrypoint 清单;
  • 统一 Skill 执行器或旧 Skill Runtime。

设备私有状态和 AgentDock 环境值只属于宿主,不进入包。

12. 递增版本

  • 只修正文错字且不影响行为判断,可递增补丁版本;
  • 新增兼容能力、变量、动作或引用,递增次版本;
  • 改变职责边界、移除既有能力或引入不兼容流程,递增主版本。

安装前比较当前已安装版本和新源码。不得用相同版本覆盖不同内容。

13. 验证和本地安装

至少完成:

  1. 检查目录和 Frontmatter;
  2. 检查版本正确递增;
  3. 检查禁止文件、符号链接和真实 secret;
  4. 对辅助脚本执行语法检查和自带测试;
  5. 运行 skill-authoring lint,确认 portable=true 并审查 warning;
  6. 使用 skill_package validate 校验包级合法性;
  7. 使用 skill_package install 安装并激活;
  8. 通过 agentdock_context 验证名称和描述进入轻量索引;
  9. 通过 read_file skill://<name>/SKILL.md 验证当前激活正文;
  10. 有引用时读取至少一份引用;
  11. 有辅助脚本时,用 exec_command skill=<name> 对当前激活版本运行只读 status;
  12. 运行一个代表性低风险动作,无法运行时记录真实原因。

skill_package validate 负责包能否合法安装;本 Skill 的 lint 负责第一方创作质量和可移植性,两者不能互相替代。

环境变量

本 Skill 自身不需要环境变量。它要求被编写的目标 Skill 声明业务变量,并由运行宿主注入当前子进程。

完成标准

只有同时满足以下条件才算完成:

  • 描述可稳定触发且边界清楚;
  • 可移植核心不依赖 AgentDock 私有目录或工具参数;
  • 包内脚本可从 Skill 根目录使用相对路径运行;
  • skill-authoring lint 返回 portable=true,warning 已逐项处理;
  • 安全检查、测试和 skill_package validate 通过;
  • 新版本已安装并激活;
  • agentdock_context 和 skill:// 读取的是新版本;
  • 当前激活版本的真实只读验证通过;
  • 没有秘密、设备私有状态或旧式架构回流。
Files (agentdock)
  • references
    • skill-package-spec.md 6.5 KB
      # AgentDock Skill 包规范
      
      本规范供 `skill-authoring` 创建或升级第一方 Skill。当前架构是纯文档 Skill,并明确区分可移植核心与 AgentDock 宿主适配。
      
      ## 1. 架构边界
      
      标准链路:
      
      ```text
      宿主发现并读取 SKILL.md
      → 模型理解流程和约束
      → 宿主选择真实工具
      → 必要时在 Skill 包根目录执行辅助脚本
      ```
      
      Skill 负责说明“应该怎样做”;工具负责真实执行。包内脚本只是模型可选择调用的资源。
      
      AgentDock 的适配链路是:
      
      ```text
      agentdock_context
      → read_file skill://<name>/SKILL.md
      → 模型理解流程
      → exec_command skill=<name> / file_edit / 浏览器 / MCP 等真实工具
      ```
      
      AgentDock 适配不是目标 Skill 的核心运行依赖。
      
      ## 2. 可移植核心契约
      
      目标 Skill 默认只依赖:
      
      - `SKILL.md`;
      - 包内相对路径;
      - 当前进程环境变量;
      - 运行宿主提供的命令、文件、浏览器或远端工具能力。
      
      有根目录脚本时,执行示例应使用:
      
      ```bash
      python3 run.py
      ```
      
      禁止把以下内容作为核心契约:
      
      - AgentDock 已安装版本绝对路径;
      - 固定版本号才能定位脚本;
      - `AGENTDOCK_HOME` 或 `AGENTDOCK_SKILL_DIR`;
      - `skill_env`、`exec_command`、`skill://`;
      - 固定用户绝对路径;
      - 主动读取宿主私有环境文件。
      
      这些 AgentDock 术语只能出现在独立、可删除的宿主适配或验证说明中。
      
      ## 3. 最小包结构
      
      ```text
      skills/<skill-name>/
      └── SKILL.md
      ```
      
      按需扩展:
      
      ```text
      skills/<skill-name>/
      ├── SKILL.md
      ├── references/
      ├── scripts/
      ├── run.py
      └── tests/
      ```
      
      不要创建空目录。
      
      ## 4. SKILL.md Schema
      
      当前必填 Frontmatter:
      
      ```yaml
      ---
      name: example-skill
      description: 清楚说明何时使用、解决什么问题
      version: 1.0.0
      ---
      ```
      
      规则:
      
      - `name` 匹配 `^[a-z][a-z0-9-]{1,62}$`;
      - `description` 非空,并支持模型稳定选择;
      - `version` 是语义化版本;
      - Markdown 正文非空;
      - 当前解析器只读取 `name`、`description`、`version`;
      - 不把未经支持的字段当作正式契约。
      
      ## 5. 版本和不可变性
      
      正文、描述、引用、脚本、测试代表的行为契约、依赖或平台要求发生变化时必须递增版本。
      
      同名同版本安装包内容必须相同。AgentDock 拒绝用不同内容覆盖已安装的同一版本。
      
      ## 6. 环境变量契约
      
      需要配置时,目标 `SKILL.md` 声明:
      
      ```markdown
      ## 环境变量
      
      | 变量 | 类型 | 必填 | 说明 |
      |---|---|---:|---|
      | EXAMPLE_BASE_URL | config | 是 | 服务地址 |
      | EXAMPLE_API_KEY | secret | 是 | API Key |
      ```
      
      Skill 包只包含变量名称、类型、必填性、用途和缺失行为。脚本只从当前进程环境读取变量,不读取宿主环境文件。
      
      AgentDock 将环境值保存在独立私有目录,通过 `skill_package env_*` 管理;执行时由 `exec_command skill=<name>` 只注入本次子进程。该保存方式不属于目标 Skill 的通用契约。
      
      ## 7. 私有状态边界
      
      运行状态、缓存、会话、数据库、下载文件和其他设备私有数据不得被打包。
      
      在 AgentDock 中它们位于独立的 Skill 数据目录,但目标 Skill 应通过业务变量、用户输入或宿主提供的工作目录获得必要路径,不硬编码 AgentDock 私有目录。
      
      ## 8. 包内禁止项
      
      禁止:
      
      - `agentdock.yaml`;
      - `.env` 或其他真实环境值文件;
      - 密码、Token、Cookie、私钥;
      - `__pycache__`、`*.pyc`、`node_modules`;
      - 编译产物和大体积生成文件;
      - 符号链接;
      - 固定用户绝对路径;
      - 隐蔽下载并执行;
      - 未说明的数据上传、删除或权限修改;
      - `skill_run`、`skill_env_manage`、`AGENTDOCK_OPERATION`、`PLUGIN_*`;
      - 旧式 `operation`、`entrypoint` 清单和旧 Skill Runtime。
      
      ## 9. 辅助脚本契约
      
      推荐输入:
      
      ```json
      {
        "skill_action": "status"
      }
      ```
      
      要求:
      
      - stdin 是 JSON 对象;
      - 顶层动作字段为 `skill_action`;
      - 秘密只从当前进程环境读取;
      - 不把秘密放入命令行参数、输出或日志;
      - stdout 返回结构化 JSON;
      - 错误包含稳定 `code` 和可读 `message`;
      - `status` 默认只读;
      - 破坏性动作需要显式确认;
      - 网络超时、依赖缺失和平台不支持必须可诊断;
      - 包内文件通过相对路径或脚本自身目录定位。
      
      ## 10. skill-authoring lint
      
      第一方 Skill 在安装前必须运行:
      
      ```json
      {
        "skill_action": "lint",
        "source": "/path/to/skill-source"
      }
      ```
      
      硬错误包括:
      
      - 硬编码 AgentDock 已安装版本路径;
      - 依赖 AgentDock 专属目录变量;
      - 主动读取 AgentDock 环境文件;
      - 固定用户绝对路径。
      
      警告包括:
      
      - 目标 `SKILL.md` 出现 AgentDock 专属工具或 URI;
      - 包内有 `run.py`,但文档缺少相对执行说明;
      - 候选文本文件过大或不是 UTF-8。
      
      第一方 Skill 要求 `portable=true`,warning 必须修复或确认只属于独立宿主适配。
      
      `skill_package validate` 仍只负责包级合法性和安装门槛,不承担创作质量 lint。
      
      ## 11. 验证矩阵
      
      ### 文档和目录
      
      - 根目录存在 `SKILL.md`;
      - Frontmatter 可解析;
      - 正文非空;
      - 引用使用包内相对路径且真实存在;
      - 无空目录、符号链接和路径逃逸。
      
      ### 可移植性
      
      - `skill-authoring lint` 返回 `portable=true`;
      - 脚本从 Skill 根目录以相对命令运行;
      - 环境只从当前进程读取;
      - 移除宿主适配说明后核心流程仍完整。
      
      ### 安全
      
      - 搜索密钥、Cookie、Authorization 头和私钥标记;
      - 检查网络目标、Shell、文件写入、删除、上传和权限修改;
      - 检查依赖安装、下载后执行和混淆代码;
      - 检查包内二进制和生成文件;
      - 旧架构术语只允许出现在明确禁止或迁移说明中。
      
      ### 脚本和测试
      
      - 运行语言语法检查;
      - 运行包内测试;
      - 运行只读 `status`;
      - 验证错误不会泄露环境值。
      
      ### AgentDock 生命周期
      
      - `skill_package validate` 返回 `valid: true`;
      - `skill_package install` 成功并激活预期版本;
      - `agentdock_context` 出现正确名称和描述;
      - `read_file skill://<name>/SKILL.md` 返回当前正文;
      - 当前激活包引用可读取;
      - `exec_command skill=<name>` 能从激活包根目录运行只读检查。
      
      ## 12. 作者交付摘要
      
      交付时至少说明:
      
      - Skill 名称和新版本;
      - 触发场景和不负责范围;
      - 新增或修改文件;
      - 环境变量名称,不包含值;
      - lint、测试和包校验结果;
      - 当前激活版本;
      - 真实限制或风险。
      
  • tests
    • test_run.py 7.9 KB
      from __future__ import annotations
      
      import json
      import os
      import pathlib
      import subprocess
      import tempfile
      import unittest
      
      ROOT = pathlib.Path(__file__).resolve().parents[1]
      
      
      class SkillAuthoringLintTests(unittest.TestCase):
          def run_skill(self, payload):
              env = os.environ.copy()
              env["PYTHONDONTWRITEBYTECODE"] = "1"
              proc = subprocess.run(
                  [str(ROOT / "run.py")],
                  input=json.dumps(payload),
                  text=True,
                  capture_output=True,
                  env=env,
                  check=False,
                  timeout=5,
              )
              return proc, json.loads(proc.stdout)
      
          def make_skill(self, skill_md, run_py="print('ok')\n"):
              temp = tempfile.TemporaryDirectory()
              root = pathlib.Path(temp.name)
              (root / "SKILL.md").write_text(skill_md, encoding="utf-8")
              if run_py is not None:
                  (root / "run.py").write_text(run_py, encoding="utf-8")
              return temp, root
      
          def test_status(self):
              proc, result = self.run_skill({"skill_action": "status"})
              self.assertEqual(proc.returncode, 0, proc.stderr)
              self.assertTrue(result["ok"])
              self.assertGreater(result["lint_rule_count"], 0)
      
          def test_linter_does_not_report_its_own_rule_definitions(self):
              source = pathlib.Path(__file__).resolve().parents[1]
      
              proc, result = self.run_skill({"skill_action": "lint", "source": str(source)})
      
              self.assertEqual(proc.returncode, 0, proc.stderr)
              self.assertTrue(result["portable"])
              self.assertNotIn(
                  "AGENTDOCK_PRIVATE_DIR_DEPENDENCY",
                  {issue["code"] for issue in result["issues"] if issue["file"] == "run.py"},
              )
      
          def test_clean_portable_skill_passes(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      # Demo
      
      在 Skill 包根目录执行 `python3 run.py`,环境变量由运行宿主注入。
      """)
              self.addCleanup(temp.cleanup)
      
              proc, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertEqual(proc.returncode, 0, proc.stderr)
              self.assertTrue(result["portable"])
              self.assertEqual(result["error_count"], 0)
              self.assertEqual(result["warning_count"], 0)
      
          def test_root_install_receipt_is_ignored(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      在 Skill 包根目录执行 `python3 run.py`。
      """)
              self.addCleanup(temp.cleanup)
              (root / ".agentdock-install.json").write_text(
                  json.dumps({"source": "/Users/alice/agentdock-skills/skills/demo-skill"}),
                  encoding="utf-8",
              )
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertTrue(result["portable"])
              self.assertEqual(result["error_count"], 0)
              self.assertEqual(result["checked_files"], 2)
      
          def test_nested_install_receipt_name_is_still_scanned(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      在 Skill 包根目录执行 `python3 run.py`。
      """)
              self.addCleanup(temp.cleanup)
              references = root / "references"
              references.mkdir()
              (references / ".agentdock-install.json").write_text(
                  json.dumps({"source": "/Users/alice/private/demo-skill"}),
                  encoding="utf-8",
              )
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertFalse(result["portable"])
              self.assertIn("FIXED_USER_ABSOLUTE_PATH", {issue["code"] for issue in result["issues"]})
      
          def test_hardcoded_install_path_and_env_file_access_fail(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      运行 `python3 ~/.agentdock/skill-store/installed/demo-skill/1.0.0/run.py`。
      """, "from pathlib import Path\nPath('~/.agentdock/env/skill/demo-skill.env').expanduser().read_text()\n")
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              codes = {issue["code"] for issue in result["issues"]}
              self.assertFalse(result["portable"])
              self.assertIn("HARDCODED_AGENTDOCK_INSTALL_PATH", codes)
              self.assertIn("AGENTDOCK_ENV_FILE_ACCESS", codes)
      
          def test_agentdock_home_installed_path_fails(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      ```bash
      SKILL_DIR="$AGENTDOCK_HOME/skill-store/installed/demo-skill/1.0.0"
      python3 "$SKILL_DIR/run.py"
      ```
      """)
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertFalse(result["portable"])
              self.assertIn("HARDCODED_AGENTDOCK_INSTALL_PATH", {issue["code"] for issue in result["issues"]})
      
          def test_agentdock_private_directory_dependency_fails(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      在 Skill 包根目录执行 `python3 run.py`。
      """, "import os\nstate_dir = os.environ.get('AGENTDOCK_DIR')\n")
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertFalse(result["portable"])
              self.assertIn("AGENTDOCK_PRIVATE_DIR_DEPENDENCY", {issue["code"] for issue in result["issues"]})
      
          def test_explicit_prohibition_list_is_not_reported_as_dependency(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      在 Skill 包根目录执行 `python3 run.py`。
      
      禁止把以下内容作为运行依赖:
      
      - `AGENTDOCK_SKILL_DIR`
      - `AGENTDOCK_DIR`
      - `~/.agentdock/skill-store/installed/demo-skill/1.0.0`
      """)
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertTrue(result["portable"])
              self.assertEqual(result["error_count"], 0)
      
          def test_unrelated_advice_does_not_hide_real_dependency(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      不要泄露密钥。
      
      运行时读取 AGENTDOCK_SKILL_DIR 并执行 `python3 run.py`。
      """)
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertFalse(result["portable"])
              self.assertIn("AGENTDOCK_SKILL_DIR_DEPENDENCY", {issue["code"] for issue in result["issues"]})
      
          def test_agentdock_adapter_terms_are_warnings(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      通用执行:`python3 run.py`。
      AgentDock 可用 exec_command、skill_env 和 skill://demo-skill/references/api.md 验证。
      """)
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              codes = {issue["code"] for issue in result["issues"]}
              self.assertTrue(result["portable"])
              self.assertEqual(result["warning_count"], 3)
              self.assertEqual(codes, {
                  "AGENTDOCK_EXEC_COMMAND_USAGE",
                  "AGENTDOCK_SKILL_ENV_USAGE",
                  "AGENTDOCK_SKILL_URI_USAGE",
              })
      
          def test_missing_relative_run_instruction_warns(self):
              temp, root = self.make_skill("""---
      name: demo-skill
      description: Demo.
      version: 1.0.0
      ---
      
      # Demo
      """)
              self.addCleanup(temp.cleanup)
      
              _, result = self.run_skill({"skill_action": "lint", "source": str(root)})
      
              self.assertTrue(result["portable"])
              self.assertIn("MISSING_PORTABLE_EXECUTION", {issue["code"] for issue in result["issues"]})
      
          def test_unknown_action_fails_with_structured_error(self):
              proc, result = self.run_skill({"skill_action": "unknown"})
              self.assertNotEqual(proc.returncode, 0)
              self.assertEqual(result["error"]["code"], "UNKNOWN_ACTION")
      
      
      if __name__ == "__main__":
          unittest.main()
      
  • run.py 10.9 KB
    #!/usr/bin/env python3
    from __future__ import annotations
    
    import json
    import re
    import sys
    from dataclasses import dataclass
    from pathlib import Path
    
    VERSION = "1.1.3"
    MAX_FILES = 500
    MAX_TEXT_BYTES = 1 << 20
    HOST_METADATA_FILES = {".agentdock-install.json"}
    TEXT_SUFFIXES = {
        ".bash", ".css", ".go", ".html", ".ini", ".js", ".json", ".jsx", ".md",
        ".py", ".rs", ".sh", ".toml", ".ts", ".tsx", ".txt", ".yaml", ".yml", ".zsh",
    }
    
    
    @dataclass(frozen=True)
    class Rule:
        code: str
        severity: str
        pattern: re.Pattern[str]
        message: str
        suggestion: str
        skill_md_only: bool = False
    
    
    RULES = (
        Rule(
            "HARDCODED_AGENTDOCK_INSTALL_PATH",
            "error",
            re.compile(
                r"(?:"
                r"(?:~|\$HOME|\$\{HOME\}|/[A-Za-z0-9._-]+)?/?\.agentdock"
                r"|\$AGENTDOCK_HOME|\$\{AGENTDOCK_HOME\}"
                r")/skill-store/installed/[a-z][a-z0-9-]*/v?\d+\.\d+\.\d+",
                re.I,
            ),
            "不应硬编码 AgentDock 已安装版本目录。",
            "改为从 Skill 包根目录执行相对命令,例如 `python3 run.py`。",
        ),
        Rule(
            "AGENTDOCK_SKILL_DIR_DEPENDENCY",
            "error",
            re.compile(r"\bAGENTDOCK_SKILL_DIR\b"),
            "Skill 核心运行流程不应依赖 AgentDock 专属目录变量。",
            "让运行宿主切换到 Skill 包根目录,包内只使用相对路径。",
        ),
        Rule(
            "AGENTDOCK_PRIVATE_DIR_DEPENDENCY",
            "error",
            re.compile(r"\bAGENTDOCK_(?:DIR|HOME)\b"),
            "Skill 核心运行流程不应依赖 AgentDock 私有目录变量。",
            "改用业务专属环境变量、XDG 目录或包内相对路径。",
        ),
        Rule(
            "AGENTDOCK_ENV_FILE_ACCESS",
            "error",
            re.compile(r"(?:source|open|read_text|read_bytes|Path\s*\().{0,120}\.agentdock/env/skill/|\.agentdock/env/skill/.{0,120}(?:source|open|read_text|read_bytes)", re.I),
            "Skill 脚本不得主动读取或 source AgentDock 私有环境文件。",
            "只从当前进程环境读取变量,由运行宿主负责注入。",
        ),
        Rule(
            "FIXED_USER_ABSOLUTE_PATH",
            "error",
            re.compile(r"(?:/Users/[A-Za-z0-9._-]+/|/home/[A-Za-z0-9._-]+/|[A-Za-z]:\\Users\\[A-Za-z0-9._-]+\\)"),
            "Skill 包中不应出现固定用户绝对路径。",
            "改用包内相对路径、用户输入或明确的业务环境变量。",
        ),
        Rule(
            "AGENTDOCK_SKILL_ENV_USAGE",
            "warning",
            re.compile(r"\bskill_env\b"),
            "SKILL.md 出现 AgentDock 专属 `skill_env`。",
            "把通用运行契约写成宿主注入环境;AgentDock 适配仅作为可选说明。",
            skill_md_only=True,
        ),
        Rule(
            "AGENTDOCK_EXEC_COMMAND_USAGE",
            "warning",
            re.compile(r"\bexec_command\b"),
            "SKILL.md 出现 AgentDock 专属 `exec_command`。",
            "核心流程使用相对命令;宿主工具调用只放在独立适配说明中。",
            skill_md_only=True,
        ),
        Rule(
            "AGENTDOCK_SKILL_URI_USAGE",
            "warning",
            re.compile(r"\bskill://"),
            "SKILL.md 使用 AgentDock 专属 `skill://` 资源地址。",
            "核心文档引用包内相对路径,例如 `references/api.md`。",
            skill_md_only=True,
        ),
    )
    
    
    def emit(value: object) -> None:
        print(json.dumps(value, ensure_ascii=False, separators=(",", ":")))
    
    
    def fail(code: str, message: str, details: object | None = None) -> None:
        error: dict[str, object] = {"code": code, "message": message}
        if details is not None:
            error["details"] = details
        emit({"ok": False, "error": error})
        raise SystemExit(1)
    
    
    def load_input() -> dict[str, object]:
        raw = sys.stdin.read().strip()
        if not raw:
            return {}
        try:
            value = json.loads(raw)
        except json.JSONDecodeError as exc:
            fail("INVALID_INPUT", "Input is not valid JSON", {"reason": str(exc)})
        if not isinstance(value, dict):
            fail("INVALID_INPUT", "Input must be a JSON object")
        return value
    
    
    def text_files(source: Path) -> list[Path]:
        files: list[Path] = []
        for path in sorted(source.rglob("*")):
            if path.is_symlink() or not path.is_file():
                continue
            if path.parent == source and path.name in HOST_METADATA_FILES:
                continue
            if path.name == "SKILL.md" or path.suffix.lower() in TEXT_SUFFIXES:
                files.append(path)
            if len(files) > MAX_FILES:
                fail("TOO_MANY_FILES", f"Skill contains more than {MAX_FILES} text files")
        return files
    
    
    def line_number(content: str, offset: int) -> int:
        return content.count("\n", 0, offset) + 1
    
    
    ADVISORY_MARKERS = (
        "不得", "禁止", "不应", "不要", "明确废弃", "硬错误", "检查以下",
        "must not", "do not", "forbidden", "prohibited", "reject",
    )
    LIST_ITEM_PATTERN = re.compile(r"^(?:[-*+]|\d+\.)\s+")
    
    
    def has_advisory_marker(value: str) -> bool:
        lowered = value.lower()
        return any(marker in lowered for marker in ADVISORY_MARKERS)
    
    
    def markdown_match_is_advisory(content: str, start: int) -> bool:
        lines = content.splitlines()
        line_index = content.count("\n", 0, start)
        current = lines[line_index].strip() if line_index < len(lines) else ""
        if has_advisory_marker(current):
            return True
        if not LIST_ITEM_PATTERN.match(current):
            return False
    
        # 列表中的禁止项只继承同一列表前的说明句,不读取任意邻近段落。
        for previous in reversed(lines[:line_index]):
            stripped = previous.strip()
            if not stripped:
                continue
            if LIST_ITEM_PATTERN.match(stripped):
                continue
            return has_advisory_marker(stripped)
        return False
    
    
    def is_advisory_match(relative: str, content: str, start: int, end: int) -> bool:
        if relative.startswith("tests/"):
            return True
    
        # lint 实现自身会包含规则正则;这里只跳过完整 Rule(...) 定义,不跳过普通脚本中的真实使用。
        rule_start = content.rfind("Rule(", 0, start)
        if rule_start >= 0:
            rule_end = content.find("\n    ),", rule_start)
            if rule_end >= start:
                return True
    
        line_start = content.rfind("\n", 0, start) + 1
        line_end = content.find("\n", end)
        if line_end < 0:
            line_end = len(content)
        line = content[line_start:line_end]
        if "re.compile(" in line and "Rule(" in content[max(0, line_start - 240):line_start]:
            return True
    
        return relative.endswith(".md") and markdown_match_is_advisory(content, start)
    
    
    def scan_file(source: Path, path: Path) -> list[dict[str, object]]:
        try:
            data = path.read_bytes()
        except OSError as exc:
            fail("READ_FAILED", "Unable to read Skill file", {"file": str(path), "reason": str(exc)})
        if len(data) > MAX_TEXT_BYTES:
            return [{
                "code": "TEXT_FILE_TOO_LARGE",
                "severity": "warning",
                "file": path.relative_to(source).as_posix(),
                "line": 1,
                "message": f"文本文件超过 {MAX_TEXT_BYTES} 字节,未执行内容检查。",
                "suggestion": "拆分或删除不必要的大文件后重新运行 lint。",
            }]
        try:
            content = data.decode("utf-8")
        except UnicodeDecodeError:
            return [{
                "code": "NON_UTF8_TEXT_FILE",
                "severity": "warning",
                "file": path.relative_to(source).as_posix(),
                "line": 1,
                "message": "候选文本文件不是 UTF-8,未执行内容检查。",
                "suggestion": "将 Skill 文档和脚本保存为 UTF-8。",
            }]
    
        relative = path.relative_to(source).as_posix()
        issues: list[dict[str, object]] = []
        for rule in RULES:
            if rule.skill_md_only and relative != "SKILL.md":
                continue
            for match in rule.pattern.finditer(content):
                if is_advisory_match(relative, content, match.start(), match.end()):
                    continue
                issues.append({
                    "code": rule.code,
                    "severity": rule.severity,
                    "file": relative,
                    "line": line_number(content, match.start()),
                    "message": rule.message,
                    "suggestion": rule.suggestion,
                })
                break
        return issues
    
    
    def check_portable_execution(source: Path, skill_md: str) -> list[dict[str, object]]:
        entry = source / "run.py"
        if not entry.is_file():
            return []
        if re.search(r"(?:python3?|\./)\s+(?:\./)?run\.py\b", skill_md):
            return []
        return [{
            "code": "MISSING_PORTABLE_EXECUTION",
            "severity": "warning",
            "file": "SKILL.md",
            "line": 1,
            "message": "包内存在 run.py,但 SKILL.md 没有说明从 Skill 根目录使用相对路径执行。",
            "suggestion": "增加通用执行示例,例如 `python3 run.py`,并说明环境由运行宿主注入。",
        }]
    
    
    def lint(source_value: object) -> dict[str, object]:
        if not isinstance(source_value, str) or not source_value.strip():
            fail("INVALID_INPUT", "source is required for lint")
        source = Path(source_value).expanduser().resolve()
        if not source.is_dir():
            fail("SOURCE_NOT_FOUND", "source must be an existing Skill directory", {"source": str(source)})
        skill_md_path = source / "SKILL.md"
        if not skill_md_path.is_file():
            fail("SKILL_DOCUMENT_MISSING", "source does not contain SKILL.md", {"source": str(source)})
    
        files = text_files(source)
        issues: list[dict[str, object]] = []
        for path in files:
            issues.extend(scan_file(source, path))
        try:
            skill_md = skill_md_path.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError) as exc:
            fail("READ_FAILED", "Unable to read SKILL.md", {"reason": str(exc)})
        issues.extend(check_portable_execution(source, skill_md))
        issues.sort(key=lambda item: (item["severity"] != "error", str(item["file"]), int(item["line"]), str(item["code"])))
    
        error_count = sum(item["severity"] == "error" for item in issues)
        warning_count = sum(item["severity"] == "warning" for item in issues)
        return {
            "ok": True,
            "action": "lint",
            "source": str(source),
            "portable": error_count == 0,
            "error_count": error_count,
            "warning_count": warning_count,
            "issues": issues,
            "checked_files": len(files),
            "skill_version": VERSION,
        }
    
    
    def main() -> None:
        request = load_input()
        action = str(request.get("skill_action", "status")).strip().lower()
        if action == "status":
            emit({
                "ok": True,
                "action": "status",
                "skill_version": VERSION,
                "lint_rule_count": len(RULES) + 1,
                "environment_required": False,
            })
            return
        if action == "lint":
            emit(lint(request.get("source")))
            return
        fail("UNKNOWN_ACTION", "Unsupported skill_action", {"action": action, "allowed": ["status", "lint"]})
    
    
    if __name__ == "__main__":
        main()
    
  • SKILL.md 11.4 KB
    ---
    name: skill-authoring
    description: 创建、设计、修改、升级、重构和验证 AgentDock Skill 时使用;负责可移植核心、文档、引用、辅助脚本、测试、版本和本地安装验证。
    version: 1.2.0
    ---
    
    # Skill Authoring
    
    用于创建或维护 AgentDock 第一方 Skill。Skill 的本体是模型可读取的说明文档;工具负责真实检查、编辑、命令执行、打包、安装和验证。
    
    目标 Skill 应由两部分构成:
    
    ```text
    可移植核心契约
    +
    可选的宿主适配说明
    ```
    
    移除 AgentDock 专属适配说明后,Skill 的业务流程、包内引用、环境变量契约和辅助脚本仍应完整可用。
    
    ## 何时使用
    
    使用本 Skill 处理:
    
    - 创建新的第一方 Skill;
    - 修改、重构或升级现有 Skill;
    - 调整触发描述、正文流程、引用资料或辅助脚本;
    - 补充测试、示例、安全约束和可移植性检查;
    - 递增版本并完成源码侧与当前激活版本验证。
    
    不要使用本 Skill 处理第三方 Skill 的正式安全审查、真实凭据配置或已安装版本回滚。这些属于 `skill-installation`。
    
    ## 核心原则
    
    1. 先定义模型何时应该选择该 Skill,再写正文。
    2. Skill 只描述方法、边界和工具选择,不承担统一执行职责。
    3. Skill 核心契约必须与宿主无关;包内文件使用相对路径,环境由运行宿主注入。
    4. 简单 Skill 优先只有一份 `SKILL.md`;只有确有需要时才增加引用、脚本或测试。
    5. 修改正文、引用、脚本或行为后必须递增语义化版本。
    6. 同名同版本内容必须保持不可变。
    7. 环境值、设备状态和运行数据不得进入 Skill 包。
    8. 所有验证都要落到当前已安装并激活的版本,不能只看源码目录。
    9. 第一方 Skill 必须通过本 Skill 的 `lint`,不能只通过包安装校验。
    
    完整规范见包内 `references/skill-package-spec.md`。
    
    ## 标准流程
    
    ### 1. 理解需求和触发条件
    
    先明确:
    
    - 用户真正要解决的问题;
    - 模型在什么请求下应选择该 Skill;
    - 哪些相邻任务不属于该 Skill;
    - 需要调用哪些真实工具;
    - 是否需要辅助脚本、引用资料或测试;
    - 是否涉及网络、写入、删除、凭据或高风险动作。
    
    不要用“管理某能力全生命周期”这类宽泛描述。`description` 必须让模型能稳定判断何时选中它。
    
    ### 2. 确定职责边界
    
    正文至少说明:
    
    - 适用场景和不适用场景;
    - 读取或修改的对象;
    - 默认只读行为;
    - 写操作和破坏性操作的确认规则;
    - 失败时需要返回的证据。
    
    一个 Skill 应围绕一个稳定能力边界组织。需求已经跨越独立职责时,应拆成多个 Skill。
    
    ### 3. 创建源码目录
    
    普通第一方和社区 Skill 默认放在独立的 `agentdock-skills` 仓库:
    
    ```text
    skills/<skill-name>/
    ```
    
    只有随 AgentDock 安装包自举、与运行时版本强绑定的核心 Skill 才放在 AgentDock 主仓库:
    
    ```text
    core-skills/<skill-name>/
    ```
    
    按需选择结构:
    
    ```text
    skills/<skill-name>/
    └── SKILL.md
    ```
    
    ```text
    skills/<skill-name>/
    ├── SKILL.md
    ├── references/
    ├── scripts/
    └── tests/
    ```
    
    ```text
    skills/<skill-name>/
    ├── SKILL.md
    ├── run.py
    └── tests/
    ```
    
    不要为了形式创建空目录,也不要把普通集成重新放回 AgentDock 主仓库。
    
    ### 4. 编写 Frontmatter
    
    当前 AgentDock 正式解析:
    
    ```yaml
    ---
    name: example-skill
    description: 清楚说明何时使用、解决什么问题
    version: 1.0.0
    ---
    ```
    
    要求:
    
    - `name` 使用稳定、简短、全小写的连字符名称;
    - `description` 同时覆盖触发场景和能力边界;
    - `version` 使用语义化版本;
    - Frontmatter 后必须有非空 Markdown 正文;
    - 不增加当前解析器未支持的环境变量或执行字段。
    
    ### 5. 编写可移植核心
    
    目标 Skill 的正文和脚本默认只假设:
    
    - 当前工作目录是 Skill 包根目录;
    - 包内资源可通过相对路径访问;
    - 环境变量来自当前进程环境;
    - 运行宿主负责选择工具、切换目录和注入环境;
    - 不依赖 AgentDock 的安装目录、状态目录或专属变量。
    
    有根目录脚本时,通用执行示例应写成:
    
    ```bash
    printf '%s' '{"skill_action":"status"}' | python3 run.py
    ```
    
    不得把以下内容作为核心运行前提:
    
    - `~/.agentdock/skill-store/installed/...`;
    - 固定安装版本号;
    - `AGENTDOCK_DIR`、`AGENTDOCK_HOME` 或 `AGENTDOCK_SKILL_DIR` 用于定位包内脚本或私有数据;
    - `skill_env`、`exec_command` 或 `skill://`;
    - 固定用户绝对路径;
    - 主动读取或 `source` AgentDock 私有环境文件。
    
    AgentDock 工具调用可以出现在单独的“AgentDock 适配/验证”说明中,但删除该部分后,Skill 仍必须可用。
    
    ### 6. 声明环境变量
    
    每个需要配置的目标 Skill 都必须在正文中明确声明变量:
    
    ```markdown
    ## 环境变量
    
    | 变量 | 类型 | 必填 | 说明 |
    |---|---|---:|---|
    | EXAMPLE_BASE_URL | config | 是 | 服务地址 |
    | EXAMPLE_API_KEY | secret | 是 | API Key |
    ```
    
    类型至少区分 `config` 和 `secret`,并说明缺失变量时哪些能力不可用。
    
    目标 Skill 只声明变量名和用途,不声明 AgentDock 私有保存路径,不保存真实值。辅助脚本只从当前进程环境读取变量。
    
    在 AgentDock 本地验证时,环境值由 `skill_package env_set/env_unset/env_list` 管理,并由 `exec_command` 的 `skill` 上下文注入本次子进程。环境不会写入 AgentDock 主进程或系统环境。
    
    ### 7. 编写引用资料
    
    适合放入 `references/` 的内容包括:
    
    - 稳定协议说明;
    - 较长检查清单;
    - API 字段或错误码表;
    - 不需要每次完整加载的背景资料。
    
    目标 `SKILL.md` 使用包内相对路径,例如:
    
    ```text
    references/api.md
    ```
    
    不要把 `skill://<name>/...` 写成核心引用契约,也不要依赖另一个 Skill 的安装目录。AgentDock 在验证当前激活包时可通过 `read_file skill://<name>/...` 读取资源。
    
    ### 8. 编写辅助脚本
    
    辅助脚本只是可选资源,不是 Skill 本体。需要脚本时:
    
    - 输入使用 stdin JSON 对象;
    - 顶层动作字段统一使用 `skill_action`;
    - 密钥只从当前进程环境读取;
    - 不通过命令行参数传递秘密;
    - 输出结构化 JSON;
    - 错误返回稳定 `code` 和可读 `message`;
    - 状态检查优先只读;
    - 写操作遵守用户确认规则;
    - 日志和错误不得回显秘密;
    - 包内文件使用相对路径或基于脚本自身目录定位。
    
    推荐输入:
    
    ```json
    {
      "skill_action": "status"
    }
    ```
    
    通用执行在 Skill 包根目录运行:
    
    ```bash
    python3 run.py
    ```
    
    AgentDock 验证时使用 `exec_command` 的 `skill: "<skill-name>"` 绑定当前激活目录与独立环境,不手工解析版本目录。
    
    ### 9. 运行可移植性 lint
    
    本 Skill 的 `run.py` 提供:
    
    - `status`:报告 lint 版本和规则数量;
    - `lint`:检查目标 Skill 的可移植核心和宿主绑定问题。
    
    输入示例:
    
    ```json
    {
      "skill_action": "lint",
      "source": "/path/to/agentdock-skills/skills/example-skill"
    }
    ```
    
    结果包含:
    
    - `portable`;
    - `error_count`;
    - `warning_count`;
    - 每条 issue 的 `code`、`severity`、文件、行号、说明和修复建议。
    
    硬编码已安装版本目录、依赖 AgentDock 专属目录变量、主动读取 AgentDock 环境文件和固定用户绝对路径属于 error。AgentDock 专属工具或 URI 出现在目标 `SKILL.md` 中属于 warning,需要确认它们只存在于可选适配说明。
    
    对已安装目录运行 lint 时,会忽略包根目录下由 AgentDock 安装器生成的 `.agentdock-install.json`。该文件属于宿主安装回执,不是 Skill 包内容;同名文件出现在包内其他目录时仍会正常扫描。
    
    在 AgentDock 中,安装新版 `skill-authoring` 后可这样运行:
    
    ```text
    exec_command
      skill: skill-authoring
      cmd: python3 run.py
      stdin: {"skill_action":"lint","source":"/path/to/source"}
    ```
    
    创建或修改第一方 Skill 时,`portable` 必须为 `true`;warning 必须逐项修复或说明为什么属于可选宿主适配。
    
    ### 10. 编写测试
    
    测试覆盖真实风险,至少考虑:
    
    - Frontmatter 和正文可解析;
    - 输入不是 JSON 对象时明确失败;
    - 未知 `skill_action` 明确失败;
    - 缺失环境变量时只报告变量名,不泄露值;
    - 只读状态检查不产生写入;
    - 破坏性动作缺少确认时拒绝;
    - 日志和错误不包含秘密;
    - 平台或依赖缺失时返回可诊断信息;
    - 包从根目录使用相对命令运行;
    - 脚本不依赖 AgentDock 私有目录。
    
    ### 11. 安全和质量检查
    
    提交前检查:
    
    - 包内没有真实密码、Token、Cookie、认证缓存或会话文件;
    - 没有 `.env`、缓存、数据库、截图、下载文件和运行结果;
    - 没有 `__pycache__`、`*.pyc`、`node_modules` 或编译产物;
    - 没有固定用户绝对路径、符号链接或路径逃逸;
    - 没有旧式统一执行协议、旧式环境工具示例或旧清单;
    - 网络目标、文件写入、删除、上传和权限变化均被明确说明;
    - 依赖安装不会被隐藏执行。
    
    明确禁止生成或恢复:
    
    - `agentdock.yaml`;
    - `skill_run`;
    - `skill_env_manage`;
    - `AGENTDOCK_OPERATION`;
    - `PLUGIN_*` 旧协议;
    - 旧式 `operation` 或 `entrypoint` 清单;
    - 统一 Skill 执行器或旧 Skill Runtime。
    
    设备私有状态和 AgentDock 环境值只属于宿主,不进入包。
    
    ### 12. 递增版本
    
    - 只修正文错字且不影响行为判断,可递增补丁版本;
    - 新增兼容能力、变量、动作或引用,递增次版本;
    - 改变职责边界、移除既有能力或引入不兼容流程,递增主版本。
    
    安装前比较当前已安装版本和新源码。不得用相同版本覆盖不同内容。
    
    ### 13. 验证和本地安装
    
    至少完成:
    
    1. 检查目录和 Frontmatter;
    2. 检查版本正确递增;
    3. 检查禁止文件、符号链接和真实 secret;
    4. 对辅助脚本执行语法检查和自带测试;
    5. 运行 `skill-authoring lint`,确认 `portable=true` 并审查 warning;
    6. 使用 `skill_package validate` 校验包级合法性;
    7. 使用 `skill_package install` 安装并激活;
    8. 通过 `agentdock_context` 验证名称和描述进入轻量索引;
    9. 通过 `read_file skill://<name>/SKILL.md` 验证当前激活正文;
    10. 有引用时读取至少一份引用;
    11. 有辅助脚本时,用 `exec_command skill=<name>` 对当前激活版本运行只读 `status`;
    12. 运行一个代表性低风险动作,无法运行时记录真实原因。
    
    `skill_package validate` 负责包能否合法安装;本 Skill 的 `lint` 负责第一方创作质量和可移植性,两者不能互相替代。
    
    ## 环境变量
    
    本 Skill 自身不需要环境变量。它要求被编写的目标 Skill 声明业务变量,并由运行宿主注入当前子进程。
    
    ## 完成标准
    
    只有同时满足以下条件才算完成:
    
    - 描述可稳定触发且边界清楚;
    - 可移植核心不依赖 AgentDock 私有目录或工具参数;
    - 包内脚本可从 Skill 根目录使用相对路径运行;
    - `skill-authoring lint` 返回 `portable=true`,warning 已逐项处理;
    - 安全检查、测试和 `skill_package validate` 通过;
    - 新版本已安装并激活;
    - `agentdock_context` 和 `skill://` 读取的是新版本;
    - 当前激活版本的真实只读验证通过;
    - 没有秘密、设备私有状态或旧式架构回流。
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related