Metadata-Version: 2.4
Name: skillverify
Version: 0.1.0
Summary: Agent Skill 生命周期验证套件（个人小团队版，宿主无关）
License: MIT
Project-URL: Homepage, https://github.com/yianyao/skillverify
Project-URL: Repository, https://github.com/yianyao/skillverify
Project-URL: Issues, https://github.com/yianyao/skillverify/issues
Project-URL: Specification, https://agentskills.io/specification
Keywords: agent,skill,validation,lint,review
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 验证流程指南

> 这份文档假设你**没有读过本项目的其它任何文档**。它讲的是：怎么在任意宿主的机器上
> 装配并运行这条技能验证流水线。
>
> 配套：技能本身怎么写 → 《技能编写指南.md》；**新手照着做** → 《操作手册.md》；
> 换机器 / 搬到别的宿主 → 《迁移与部署指南.md》；想弄清代码结构 → 《仓库结构说明.md》。
> 适用前提：个人小团队（≤2 人）；不需要联网（除官方校验器对账外）；不需要安装任何第三方 Python 包。

---

## 一、它是什么，解决什么问题

`skillverify` 是一条覆盖 Agent Skill **从设计到运行**的验证流水线，分五层：

| 层 | 命令 | 查什么 | 为什么需要它 |
|---|---|---|---|
| 官方规范 | `spec` | frontmatter 6 字段与命名约定 | 官方校验器只查这些 |
| 机械补检 | `lint` | 引用、上下文预算、目录卫生、脚本契约、依赖、安全、编码 | **官方完全不查**这些 |
| 评测资产 | `evals` | 官方 `evals/evals.json` 与工作区产物（输出质量）**＋** `evals/trigger-queryset.json` 与运行记录（会不会被触发） | 官方给了硬约定但**没有工具**；触发这一层官方根本不覆盖 |
| 语义评审 | `review` | 30 条提示词的判断（描述质量、可验证性、安全性…） | 机械层查不了"写得对不对" |
| 交付门禁 | `deliver` | 汇总上面的结论，决定能不能交付 | 防止"带着没查的项交付" |

**设计前提**：宿主无关（不假设特定宿主的目录、不硬编码宿主名）、纯标准库、
强制 UTF-8（Windows 上的 GBK 曾把旧工具链直接打崩）。

退出码（全族统一，便于任何 CI 消费）：

| 码 | 含义 |
|---|---|
| `0` | 全 PASS（含"不适用"） |
| `1` | 有 **FAIL**（阻断项） |
| `2` | 无 FAIL，但有 **WARN**（需人工判断）或 **SKIP**（本项未执行） |

报告里的判定只有五种：`FAIL`（阻断）、`WARN`（人工甄别）、`SKIP`（**本项未执行**，不许当成通过）、
`INFO`（不适用）、`PASS`。

## 二、跑起来

不需要 pip 安装也能用（免安装直接跑 repo）：

```bash
cd <本仓库>
python -m skillverify.cli --help
```

安装后（可选，见本仓库的打包配置）命令名就是 `skillverify`：

```bash
skillverify --help
```

> Python 版本：`discover` / `check` / `watch` / `deliver` / `hook` 需要 **≥3.11**
> （用标准库 `tomllib` 解析配置文件）；`spec` / `lint` / `evals` / `review` 在更早版本也能用。

## 三、技能放在哪里：声明式宿主适配

技能存放位置**不写死在代码里**，而是读一份声明文件 `hosts.toml`：

```bash
skillverify discover --show-config      # 看当前生效的配置（含来源顺序）
skillverify discover                    # 看发现了哪些技能、每个根命中几个
```

内置配置（随包分发）里的默认档就是官方跨宿主约定：项目级 `<项目>/.agents/skills/`、
用户级 `~/.agents/skills/`。另外预置了几个兼容档（`workbuddy`、`project-local`、
`nested-by-category`）供参考。

配置覆盖顺序（后者覆盖前者，**列表是整体替换而不是拼接**）：

```
内置 < ~/.agents/skillverify/hosts.toml < <项目>/.agents/skillverify/hosts.toml < --config <文件>
```

### 接入一个新宿主：只改配置，不改代码

假设某宿主把技能放在 `.acme-corp/skills/`：

```toml
# acme.toml
[hosts.acme-corp]
description = "Acme 布局"
project_roots = [".acme-corp/skills", ".agents/skills"]
user_roots = ["~/.acme-corp/skills", "~/.agents/skills"]
trace_dir = ".agents/skillverify"
max_depth = 1
```

```bash
skillverify discover --config acme.toml --host acme-corp
skillverify check    --config acme.toml --host acme-corp
```

字段含义：`project_roots`（相对项目目录；绝对路径原样使用）、`user_roots`（`~` 按当前用户主目录展开）、
`trace_dir`（中央留痕目录）、`max_depth`（技能根下最多下钻几层找 `SKILL.md`）。
**未知键会直接报错**（把 `project_roots` 拼成单数而静默发现 0 个技能，是这类工具最坏的失败模式）。

不想动配置文件时，用 `--root` 直接指定技能根（给出后**完全替换**配置里的根列表）：

```bash
skillverify discover --root <技能根>
```

同名技能出现在多个根时，会记 `DISC-001` 并**保留两者**（不静默覆盖）——两处并存说明不同宿主
可能加载到不同版本，这是要解决的问题，不是要掩盖的问题。

## 四、逐层跑

### 4.1 单个技能

```bash
skillverify spec  <技能目录>              # 官方规范（可加 --official 与官方 CLI 对账）
skillverify lint  <技能目录>              # 机械补检
skillverify lint  <技能目录> --scripts    # 额外实测脚本契约（会执行脚本，须显式开启）
skillverify evals <技能目录>              # 两类评测资产：官方 evals.json + 触发查询集
```

**执行层委托官方工具**：评测的**执行**（跑 with/without、算 benchmark、A/B、按结果改进）
由官方 skill-creator（或你的宿主）提供，本项目**不自研执行器**——只校验它产出的**产物形状与口径**
（`grading.json` / `timing.json` / `benchmark.json` 的字段、阈值、对账关系）。理由很直接：
官方已有执行层，重复造一个只会与它的产物格式漂移。

**要真跑评测**：本工具不执行评测。用官方 skill-creator 或你的宿主跑，再把它的入口命令交给
`evals --run-with "<命令>"` 委托执行（本工具负责限时、报退出码，并在跑完后**重新校验产物**；
命令失败记 `RUN-101` FAIL，绝不写成"不适用"）。

`evals` 同时校验两类资产：**官方输出质量评测**（`evals/evals.json`，形状由官方固定）与
**触发评测**（`evals/trigger-queryset.json` + `evals/trigger-runs.json`，本项目约定）。
两类都没有时都会给出 WARN 而不是静默通过——「没有证据」与「证据全绿」必须能区分开。
若技能里还留着旧落点（`.verification/trigger-eval/queryset.json`），报告会直接给出迁移指引。

### 4.2 整库批量

### 4.1b 机械层还会查什么（加固轮新增）

| 规则 | 查什么 | 判定 |
|---|---|---|
| `HYG-006` | `license` 字段是否过长（完整条款该放随包文件） | WARN |
| `HYG-007` | 声明了 `license` 就该有随包许可文件 | WARN |
| `SEC-008` | 隐藏字符：双向控制符（Trojan Source）/ 零宽字符 | 代码里双向控制符 **FAIL**；其余 **WARN** |
| `SCRIPT-009` | 声明了 `--dry-run` 的脚本试跑后**不得改动技能目录** | 改动 → **FAIL**（在临时副本里验证） |
| `SCRIPT-005` | 破坏性/有状态操作须有防护旗标（扫**全包代码文件**）；证据给出 `file:行（类别）：原文` 的**定位段落** | 无旗标 → WARN |
| `EVAL-010` | 断言区分度：跨轮次恒真/恒假的断言 | WARN（观测不足 3 次记 INFO） |
| `REV-012` | 评委校准：判错校准样本 | 判错 → **FAIL（结论不可用）**；没做 → INFO |



```bash
skillverify check --root <技能根> --stages all
```

`--stages` 可选 `all`（spec+lint+evals+review）/ `both`（spec+lint）/ 单阶段。
`check` 默认 `both`（日常快查）。**未发现任何技能时返回 1**——"什么都没检查"不允许看起来像通过。

### 4.3 挂载前置检查（上宿主之前）

```bash
skillverify mount --project <项目> --user-home <用户主目录>          # 查当前生效的宿主档
skillverify mount --project <项目> --skill <技能名>                  # 只查这个技能
skillverify mount --project <项目> --all-hosts                      # 逐个宿主档都查（新接宿主时用）
```

它在**仓库侧**能确定的范围里回答「这个技能放到宿主上会不会有问题」：

| 检查 | 问的是 |
|---|---|
| `MOUNT-001` | 技能**真的**落在该宿主档声明的目录里吗（不是「我以为我放进去了」） |
| `MOUNT-002` | `SKILL.md` 可解析、`name`/`description` 都读得出来且非空吗（描述为空的技能宿主会跳过） |
| `MOUNT-003` | 同一宿主档里有没有同名技能（宿主加载哪一份是不确定的） |
| `MOUNT-004` | 把三种结构损坏的 frontmatter 注入**临时副本**后，解析器会 fail-loud 吗（不触碰原目录） |

**它不验证宿主注册表与真实触发匹配**——本工具没有宿主 API、也没有宿主运行时，那两件事请在目标
宿主里人工确认。默认只查**当前生效的宿主档**；配置里的兼容档并不是你在用的布局，拿它们报
「技能不在声明目录里」只会是噪声，所以逐个档验收时要显式 `--all-hosts`。

### 4.4 库级检查（把技能集合当整体看）

单技能检查看不见两类问题，它们只在**技能一多**时才出现：

```bash
skillverify discover --project <项目>                       # 顺带打印库级结论
skillverify check --project <项目> --metadata-budget 8000   # 也可以收紧预算
```

| 检查 | 问的是 |
|---|---|
| `LIB-001` | 全部技能的 `name`+`description` 加起来多少字符？超过预算没有？宿主启动时会把它们**一起**读进上下文，超了就会**静默丢弃**某些技能（不报错、不生效） |
| `LIB-002` | 有没有两个技能的描述用词高度重合？（触发时互相抢）会点名是哪两个、并列出共同词元 |

口径写在报告里，别当成官方硬线：预算默认 **8000 字符**（本项目约定，可用 `--metadata-budget` 覆盖，
真实上限由你的宿主决定）；只计 `name`+`description`，不含宿主自身的包装开销；
`LIB-002` 是**词面**重叠（字符 bigram + 词元，纯离线），不是语义相似度——它只把
"最该人工看一遍的那几对"挑出来，词元太少的描述会被明确排除在比对之外。

### 4.5 外来技能审计（从别处拿来的技能，用之前）

```bash
skillverify audit <技能目录或技能名> --record      # 出审计单并留痕（下次可比对）
```

它把各阶段的结论组合成一张"要不要用它"的单子：**来源**（git 远端/提交，能查到就记）、
**能力清单**（脚本、网络端点、破坏性/有状态操作、疑似硬编码密钥、外部依赖）、
**判定明细**（spec/lint 的 FAIL/WARN 原样带进来）、**内容指纹**，以及库内**名字近似**提示。

**退出码**：`audit` 会把 `lint` 的结论一并带进来，所以默认（不执行脚本）返回 `2`——
那表示"脚本契约四项没执行"，与 `lint` 的口径一致；**不是**审计失败。要连脚本一起实测再加 `--scripts`
（审计外来技能时请先想清楚：那会真的执行对方的脚本）。

能力清单不是从报告文字里猜的：它来自机械层的**结构化扫描**（`security.facts()`、
`scripts.facts()`、`deps.facts()`），所以 lint 的措辞改了也不会影响审计单。口径写在单子上：
网络端点 = **代码文件**里出现且未在 frontmatter 声明的主机；破坏性/有状态操作 = 脚本里的删除/覆盖/
移动等形态；疑似密钥 = 高置信度特征命中；外部依赖 = Python 脚本导入的第三方模块。

`AUDIT-005` 有三种判定，别混淆：**PASS** 表示清单里有内容（值得看）；**INFO** 表示这是纯文档技能、
清单为空（不适用，不影响退出码）；**SKIP** 表示 lint 前置检查失败、清单**没生成**（未覆盖项）。

再审计同名的技能时，会比对指纹并明确告诉你"内容与上次不同"——别人可以在你审计之后替换内容，
这是审计留痕唯一的意义。审计单里留了一栏「来源与信任级别」**由人填写**：
工具只保证"同一份内容"与"内容变了"能被认出来，**不做**行为审计、不做信任分级。

### 4.6 开发期即时反馈

```bash
skillverify watch --root <技能根>              # 轮询（默认 1 秒），变化即复跑
skillverify watch --root <技能根> --once       # 跑一轮就退出（CI 用）
skillverify watch --root <技能根> --json       # 每轮一行 JSON，便于管道消费
```

只复跑**内容指纹变化**的技能（指纹取 stat，不读文件内容）；刻意不执行脚本
（高频复跑下反复执行脚本既慢又有副作用）。

## 五、语义评审：三档执行器，一个 schema

30 条提示词（旧体系 29 + 新增 W-17）（D 设计期 / W 编写期 / E 测试期 / R 运行期），每条带 PASS/FAIL 判据与证据要求：

```bash
skillverify review prompts                 # 全部 29 条（含判据）
skillverify review prompts --family W      # 只看编写期那 16 条
```

**档 2 · 会话任务包（推荐默认）**：

```bash
skillverify review pack <技能目录>                      # 生成任务包（默认 29 条）
skillverify review pack <技能目录> --prompts W-01,W-02  # 只评指定几条
skillverify review pack <技能目录> --split              # 每条提示词一个 Markdown（便于一条一个调用）
```

产出三件：`<技能名>-review-pack.md`（任务书）、`<技能名>-review-template.json`
（**预填好 prompt_id 的回写模板**，评审者只填结论）、`<技能名>-review-manifest.json`
（记录本轮范围与技能内容指纹）。

把任务书交给**任意 LLM 或人**——这一步不依赖任何宿主，因为任务包就是 Markdown + JSON。
填好的模板就是回写：

```bash
skillverify review collect <填好的 JSON> --skill-dir <技能目录>
skillverify review collect --dir <回写目录> --skill-dir <技能目录>   # 汇总一批
skillverify review status                                            # 看各技能的评审状态与新鲜度
```

回写 schema（三档共用）：

```json
{
  "schema": "skillverify.review/1",
  "skill": "my-skill",
  "reviewer": "你的人名或模型标识",
  "tier": "pack",
  "generated_at": "2026-10-04T10:00:00+08:00",
  "prompt_ids": ["W-01", "W-02"],
  "results": [
    {"prompt_id": "W-01", "verdict": "PASS",
     "evidence": "见 SKILL.md:12 的 description 字段，含 PDF/表单/提取三个关键词",
     "finding": "", "suggestion": ""}
  ]
}
```

**档 1 · 任意 CLI 自动**：

```bash
skillverify review run <技能目录> --runner "你的评审命令"
skillverify review run <技能目录> --runner "你的评审命令" --prompts W-01,W-13   # 只跑几条
skillverify review run <技能目录> --runner "你的评审命令" --out <回写目录>      # 落盘便于复核
skillverify review run <技能目录> --runner "你的评审命令" --collect            # 顺带写中央记录
```

它逐条把提示词送上 runner 的 **stdin**，从 **stdout** 取回一个 JSON 结论
（围栏或寒暄都能容忍，取不到就报错），组装成与档 2/档 3 **完全相同**的
`skillverify.review/1` 回写，再走同一条校验与汇总。

- **工具不内置 LLM 客户端**：一旦内置就得维护各家 API、密钥与重试，而"用哪个模型、怎么提示"
  是使用者的事。这里只做机械部分。
- runner **失败（非零退出 / 超时 / 输出不可解析）不会被写成 NA**——那会把"工具坏了"伪装成
  "本项不适用"。失败条目直接不产出，并以非零退出码报出来（`--allow-partial` 可只记 WARN）。
- 回写的 `reviewer` 写的就是**实际跑的命令**：评审必须有签署；中央记录里还会记
  `tiers`（这批结论出自哪一档）。
- 命令由你提供、以你的权限执行，与 `lint --scripts` 同类，均需显式开启。
  `--runner` 收的是**一个命令字符串**：整串用引号包住（`--runner "my-llm -m gpt-4"`）；若命令内部还需要引用（例如解释器路径含空格），改用单引号包外层或写一个包装脚本。

**档 3 · 人工兜底**：手写同一个 JSON 即可，`collect` 的校验一模一样。

`collect` 会拦下这些（这就是"评审切实有效"的机械保障）：

| 会被拦下的 | 判定 |
|---|---|
| `reviewer` 为空 | FAIL（无签署的评审等于没有评审） |
| 证据过短、或没有文件/行号/引用/计数 | FAIL（不可定位 = 无法复核） |
| 证据与判据文本高度重合 | WARN（复述判据不算证据） |
| FAIL 没写 `finding` / NA 没写理由 | FAIL |
| 同一提示词出现两条互相冲突的结论 | WARN（双评场景，须人工裁决） |
| 声明的提示词有没有结论的 | WARN（未覆盖项） |

### 让语义评审独立成一个技能包（可选）

```bash
skillverify review skill --out <输出目录>
```

生成 `skillverify-review/` 技能包（`SKILL.md` + `references/` + `assets/`），
把它放进**任意宿主**的技能目录即可加载使用——语义评审因此不绑定任何宿主。

### 执行轮次自证（`WS-008`，双跑对照必做）

执行层不自研，但**「这轮到底怎么跑的」必须留痕**：在每个 `iteration-N/` 下放一份
`run-inputs.json`（模板见 `examples/run-inputs.json`），写清四件事：

| 字段 | 写什么 |
|---|---|
| `isolation` | `隔离` / `降级` / `未执行`——**做不到就如实写降级**，不许留空 |
| `executor` | 两臂用的命令行（例如各起独立进程、禁用 Skill 工具与 resume） |
| `input_hash` | 技能目录 + 靶子任务输入的联合哈希（证明输入冻结） |
| `prompt_hashes` | 两臂提示词哈希（应**仅差技能约束行**） |

另外两个**可选**字段，登记「断言是什么时候写的」（`EVAL-011`）：

| 字段 | 写什么 |
|---|---|
| `assertions_added_at` | 断言写下的时间（ISO 8601，如 `2026-10-05T11:00:00+08:00`） |
| `outputs_produced_at` | 本轮产出输出的时间（可选）；写了它，报告会给出两者的先后关系 |

- **只认这两个显式字段**：旧实现靠文件 mtime 猜「断言何时补的」，复制/克隆/重写都会变，
  本项目**不猜时间**——不写就记 INFO（不适用），不报错也不推断；
- 写了但解析不出 ISO 8601 → `EVAL-011` WARN（无法解析的声明等于没声明）；
- **先后关系只登记、不判缺陷**：官方明确允许"先跑一轮再补断言"，「先写断言再跑」也自有道理，
  工具不替使用者选一种流程。

- 完全没提供 → `WS-008` INFO（增强项，不阻断）；**同一工作区里只提供了一部分** 或写了 `降级`/`未执行` → WARN；
- 标成 `降级` / `未执行` → WARN：**该轮只能算参考级证据，其 delta 不得当作技能带来的增益**。

### 运行台账（`AUDIT-006`，可选）

运行期观测没法自动化，但**别让台账变成摆设**：把 `run-log.md`（模板 `examples/run-log.md`）
放进中央留痕目录，`audit` 会检查两件事——**异常观察写了却没写处置**、**超过 30 天没更新**。
有台账才检查；没有记 INFO（不阻断）。
日期请按 `YYYY-MM-DD` 写（`2026-9-5` 这种非补零写法也认）：**一条日期都认不出来时**，
`audit` 会记 WARN「陈旧检查**未执行**」——"没算出来"不等于"很新鲜"。

### 评委校准（第 0 步，可选但推荐）

任务包里带 4 个答案明确的校准样本（2 应过 / 2 应挂）。先判它们并把结果填进回写的 `calibration`：
**判错 → `REV-012` FAIL，本轮结论不可用**（评委分不稳）；判对 → PASS；判了一部分 → WARN；没做 → INFO。

### 几条提示词需要的流程材料：`review material`

有几条提示词评的不是技能文件本身，而是流程产物或**技能集合**。先让工具把它们摘成材料文件：

```bash
skillverify review material <技能目录>                        # 生成到 <技能名>-review-material/
skillverify review material <技能目录> --workspace <工作区目录>  # 显式指定评测工作区
skillverify review material <技能目录> --diff-base HEAD~1       # 指定 description diff 的基线
skillverify review material <技能目录> --blind <旧版> <新版>      # 盲评：两版产物随机落到 A/B
```

| 材料 | 服务的提示词 | 来源 |
|---|---|---|
| `description-diff.md` | `E-02` | `git diff <基线> -- SKILL.md`（非 git 仓库会说明无法取 diff） |
| `revision-signals.md` | `E-03` | 工作区里的失败断言 + 人工反馈 + benchmark delta |
| `blind/`（`blind-A/`、`blind-B/`、`mapping.json`） | `E-06` | 你给的两版产物，**随机**分配；评审结束前不要看 `mapping.json` |
| `workspace-digest.md` | `E-07`、`E-08` | 工作区各 iteration 的逐次产物与聚合值，摘成一屏数字 |
| `adjacency.md` | `W-11` | **同级**技能目录里各邻居的 description 与词面重叠系数（与 `LIB-002` 同一套口径），并留一栏「边界裁决」给人填 |

生成不了的项目**记 SKIP 并逐条说明缺什么**（缺工作区？不是 git 仓库？没给两版产物？同级目录列不出来？）——
所以退出码常是 `2`：那是「有材料没生成」的正常信号，不是失败。
唯一的例外是 `adjacency.md`：同级**根本没有别的技能**时记 `INFO`（不适用），因为那时确实没有可比的对象。
`adjacency.md` 的**局限**写在材料里：只在同级找邻居，分类嵌套（`<root>/<类别>/<技能>/`）布局会漏，
那种布局请手工补一份相邻技能清单；词面重叠**不是**语义相似度，串扰与否仍由 `W-11` 判。
其余条目**只读技能目录本身**（`SKILL.md`、`references/`、`scripts/`、`evals/`），不需要额外准备。
材料不齐时在回写里**记 NA 并写明缺什么**，不要凭印象给结论。

## 六、交付门禁

`check` 是日常检查；`deliver` 是交付门禁——**没有 FAIL 且没有未覆盖项**才算通过：

```bash
skillverify deliver --root <技能根>                    # 全库
skillverify deliver --root <技能根> --strict           # 连"未覆盖项"也阻断（发行前跑一次）
skillverify deliver --root <技能根> --verbose          # 把未覆盖/待甄别项也逐条打出来
```

- **FAIL 阻断**；
- **WARN 不阻断**，但逐条写进交付记录（这就是"需人工书面甄别"的书面痕迹）；
- **SKIP 分两类**：属"未开启的可选批次/环境能力不足"的记**未覆盖项**（默认不阻断，
  `--strict` 时阻断）；其余 SKIP（本项确实没执行，例如前置失败）**一律阻断**；
- **语义评审**默认参与（含在 `--stages all` 里）；`--skip-review` 可一键跳过，且**留痕为未覆盖项**。

交付记录写到中央留痕目录（默认 `<项目>/.agents/skillverify/deliver/`）：

- `latest.json` —— 含 `rules_hash`（证明是哪套规则判的）、git commit/branch/dirty、逐技能结论、
  门禁结论（阻断项 / 未覆盖项 / 待甄别项）；
- `latest.md` —— 人读报告；
- `history.jsonl` —— 每次交付追加一行，便于 grep 趋势。

`--json` 输出的是**交付记录本身**（含门禁结论），便于 CI 直接消费。

### 执行器样例（`examples/`）

执行层不自研，但**怎么委托**有可以照抄改的模板（纯标准库、支持 `--dry-run`）：

| 模板 | 干什么 | 典型用法 |
|---|---|---|
| `examples/run_evals.py` | 双臂（with/without skill）各跑一遍、按官方布局建工作区、记实测耗时与 token | `python examples/run_evals.py --skill <技能> --cmd "claude -p {prompt}"` |
| `examples/run_triggers.py` | 把触发查询集真跑一遍并写 `trigger-runs.json` | `python examples/run_triggers.py --skill <技能> --cmd "<判定命令>"` |
| `examples/badcase_to_evals.py` | 把线上坏例子回流成一条 `evals.json` 用例 | `python examples/badcase_to_evals.py --skill <技能> --prompt … --expected …` |

三个都**不替你打分**——评分与断言是人的判断（见 `E-04`）。

### ⚑ 逐条双评（`REV-013`）

目录里 8 项标 ⚑（`W-02/04/05/08/15`、`E-04/06`、`R-02`）：要求**两条各自署名的独立结论**
（回写里写 `by` + `at`；同一人跑两遍不算）。署名缺失记 INFO（未启用），独立署名不足 2 条记 WARN，
两人结论不一致升级为待人工裁决。

> 注意：本工具**不生成**"看起来独立"的 A/B 任务包——旧体系那套 A/B 包实测**逐字相同、仅包号不同**，
> 独立性全靠人工开两个会话。这里改成机械判定署名，宁可让人真去开第二个会话。

### WARN 的裁决留痕（`WARN` = 人工甄别，不是"记一下就算了"）

`deliver` 记录里的 `adjudications` 会给出三份清单：`pending`（没人认领的 WARN）、
`void`（裁决已失效：证据变了或缺字段）、`resolved`（已拍板且证据一致）。
裁决写在 `<留痕目录>/adjudications.json`（模板 `examples/adjudications.json`），
每条必须带 `decision`（accept/fix）、`reason`、`by`、`at` 与 `evidence_hash`。
**证据一变，裁决自动回到待裁决**——签认只覆盖它当时看过的那一份证据。

### 误报的出口：豁免通道

```bash
skillverify deliver --accept <规则ID> --because "<理由>"
```

把该规则的阻断项记为「已豁免」（落盘在 `gate.waivers`），门禁随即放行；**缺理由直接拒绝**。
这是方案 §6.1「[S] 级书面说明后放行」的落地——显式、有理由、可追溯，不是静默放过。

### 接到提交上（git hook）

```bash
skillverify hook install --project <仓库目录>
skillverify hook status  --project <仓库目录>
```

装好后每次提交自动执行 `deliver --staged`（**只查本次提交涉及的技能**，按目录前缀归属）：

- 存在他人 hook 时**拒绝改动**，`--force` 会先备份成 `pre-commit.bak` 再覆盖；
- 找不到可执行的 skillverify 时**只告警不阻断**（`SKILLVERIFY_HOOK_STRICT=1` 改为故障关闭）——
  一个会把你锁死在"改不动也提交不了"状态的门禁，对两人团队比漏检更糟；
- 逃生阀：`git commit --no-verify`、`SKILLVERIFY_SKIP=1`。

只改非技能文件（例如 README）时**不会阻断**（本次没有需要检查的技能）。

## 七、全程演练（照着敲一遍）

下面 18 步用一份本机技能目录把整条流水线走一遍。把 `<>` 里的占位符换成你自己的路径，
行尾的 `# N` 是**期望退出码**，命令上方的整行注释是**这一步在干什么**。

> 这一段不是"示意"：`tests/test_docs.py` 会把这块里 `<!-- runnable -->` 标记的每条命令**真跑一遍**，
> 并核对实际退出码与标注一致。所以它既是你照着敲的脚本，也是文档不失效的保证——
> 改了命令就必须同步改期望退出码，改不对测试就红。

<!-- runnable -->
```bash
# 1) 官方规范（离线、不联网）：frontmatter 六个字段白名单、name 与父目录名一致、description 非空
skillverify spec "<技能目录>"                                                    # 0
# 2) 机械补检：引用、上下文预算、目录卫生、编码、依赖、安全。默认**不执行**脚本，
#    所以脚本契约四项没跑 → 记 SKIP，退出码为 2（未覆盖项，不是失败）
skillverify lint "<技能目录>"                                                    # 2
# 3) 同一套补检，并**实测脚本契约**：--help 可用、非法参数非零退出且错误走 stderr、重复调用一致
skillverify lint "<技能目录>" --scripts                                          # 0
# 4) 两类评测资产：官方 evals.json（输出质量）＋ 触发查询集与运行记录（会不会被触发）
skillverify evals "<技能目录>"                                                   # 0
# 5) 整库发现：按生效配置找出所有技能，看每个技能根命中几个、有没有同名冲突
skillverify discover --project "<项目>" --user-home "<用户主目录>" --root "<技能根>"   # 0
# 6) 打印**生效配置**（内置 < 用户级 < 项目级 < --config 的覆盖顺序）——接入新宿主先看它
skillverify discover --show-config                                               # 0
# 上宿主之前：技能是不是真的落在该宿主档声明的目录里、能不能被读出来、有没有同名冲突
skillverify mount --project "<项目>" --user-home "<用户主目录>" --skill "<技能名>"     # 0
# 7) 外来技能审计：组合各阶段结论 + 来源 + 能力清单 + 内容指纹，出一张决策单（--record 留痕）。
#    再审计时会比对指纹，提示「内容被换过」；明确不做行为审计与信任分级。
#    这里的 2 与第 2 步同源：默认不执行技能自带脚本，脚本契约四项记 SKIP（未覆盖项，不是失败）
skillverify audit "<技能目录>" --project "<项目>" --user-home "<用户主目录>" --record  # 2
# 8) 开发期即时反馈：只有内容指纹变化的技能才复跑；--once 跑一轮就退出（CI 用）。
#    它刻意不执行脚本（高频复跑下反复执行脚本既慢又有副作用）→ 同样记 SKIP，退出码 2
skillverify watch --project "<项目>" --user-home "<用户主目录>" --root "<技能根>" --once   # 2
# 8) 查看语义评审提示词目录（29 条，每条带 PASS/FAIL 判据与证据要求）；--family W 只看编写期 16 条
skillverify review prompts --family W                                            # 0
# 9) 档 1 执行器：把提示词逐条喂给你指定的命令，收回与档 2/档 3 同 schema 的回写（这里示范跑 1 条）
skillverify review run "<技能目录>" --runner "<评审命令>" --prompts W-01 --out "<回写目录>"   # 0
# 10) 生成几条提示词需要的**流程材料**：描述修订 diff、修订信号、盲评 A/B、工作区数字摘要
skillverify review material "<技能目录>" --out "<材料目录>" --blind "<盲评旧版>" "<盲评新版>"   # 0
# 11) 档 2 任务包：Markdown 任务书 ＋ **预填好的回写模板** ＋ manifest（含技能内容指纹）
skillverify review pack "<技能目录>" --prompts W-01,W-13 --out "<任务包目录>"        # 0
# ↓ 这一步由人或任意 LLM 完成：把 <任务包目录>/<技能名>-review-template.json 填好
# 12) 校验回写（签署、证据是否可定位、FAIL 有没有写问题…）并汇总进中央记录
skillverify review collect "<回写文件>" --skill-dir "<技能目录>" --project "<项目>" --user-home "<用户主目录>" --root "<技能根>"   # 0
# 13) 看各技能的评审状态与新鲜度：技能在评审后被改过，会提示"需重评"
skillverify review status --project "<项目>" --user-home "<用户主目录>" --root "<技能根>"   # 0
# 14) 整库总检：spec + lint + evals + review 一网打尽（all 含语义评审；--scripts 打开脚本实测）
skillverify check --project "<项目>" --user-home "<用户主目录>" --root "<技能根>" --stages all --scripts   # 0
# 15) 交付门禁：FAIL 阻断交付、WARN 逐条记账、未覆盖项列出来；并写出 latest.json/md 与 history.jsonl
skillverify deliver --project "<项目>" --user-home "<用户主目录>" --root "<技能根>"   # 0
# 16) 把"语义评审"导成一个独立技能包（SKILL.md + references/ + assets/），可放进任意宿主
skillverify review skill --out "<独立技能目录>"                                    # 0
# 17) 装 git hook：提交时只查本次改动涉及的技能，坏技能直接拦住提交
skillverify hook install --project "<项目>"                                       # 0
# 18) 看 hook 装没装、技能包版本对不对
skillverify hook status  --project "<项目>"                                       # 0
```

**分五段看**：1–4 单技能逐层检查 ｜ 5–7 整库与开发期反馈 ｜ 8–13 语义评审（含三档执行器）｜
14–15 交付门禁 ｜ 16–18 分发与自动化。

**退出码里那几个 `2`，含义都是"有项没跑"，不是失败**：

- 第 2 步是 `2`：没加 `--scripts` 时脚本契约实测**没执行**，记 SKIP（未覆盖项）。加上 `--scripts`（第 3 步）就变成 `0`。
- 第 7 步也是 `2`：`watch` 刻意不执行脚本，只做静态检查。
- 若技能没有评测资产，第 4 步会返回 `2`（`EVAL-000` 提示"没有任何质量证据"）——
  **"没有 evals"和"evals 全绿"必须能区分开**，所以这里不是 `0`。
- 第 10 步本次是 `0`：给了工作区与两版盲评产物，四项材料都产出了。若没给（没跑过评测、不是 git 仓库），
  它会记 SKIP 并返回 `2`——那是「有材料没生成」的正常信号，逐条原因写在报告里。

**三条容易踩的**：

- 第 12 步的 `--skill-dir` 不能省：它把技能目录的**内容指纹**写进评审记录，技能之后改过就会被判
  "记录过期，需重评"（按内容比对，不看文件时间）。第 13 步就是查这个状态。
- 第 15 步默认把语义评审算进来；只想跳过就加 `--skip-review`（会留痕为未覆盖项）。
- 第 16 步产出的技能包可以放进任意宿主的技能目录，语义评审因此不绑定宿主。

## 八、它**不**做什么（先说清楚）

- **不承诺在宿主内自动触发**技能激活——跨宿主没有统一机制；本项目给的是
  `watch`（改文件即时复跑）+ git hook（拦提交）+ 技能里的触发词。
- **不验证宿主注册表**：`mount` 只做仓库侧的挂载前置检查；「宿主技能列表里看不看得见」、「真实触发会不会串扰」要在目标宿主里人工确认。
- **不内置任何 LLM**：语义评审只负责生成任务包、把提示词喂给你指定的命令（档 1）、校验回写、汇总结论。用哪个模型、怎么提示，由你决定。
- **不做 46 个宿主的路径映射表**：官方 clients 页根本没有路径信息；接入宿主请用 `hosts.toml` 声明。
- **不引入独立依赖清单**：脚本依赖写在脚本里（Python 用 PEP 723；Deno 写 `npm:pkg@1.2.3`、
  带版本的 import 说明符、Ruby `bundler/inline` 的 `gem "x", "1.2.3"`——`DEP-005` 只认这些明确形态）。
- **不自研评测执行层**：`evals` 只校验资产与产物形状，**不执行、不联网**。真要跑就把执行委托出去
  （`evals --run-with <命令>`），比如 AgentV——它原生识别官方 `evals.json`；样例与配合契约见
  `examples/agentv/README.md` 与《操作手册》A4b。**代价与密钥由使用者承担，我们只做"起进程 + 限时 + 跑完复校"。**
- **不做密钥评分、域名信誉、混淆对抗**这类判据——它们"命中不等于恶意"，
  本项目只把它们记 WARN，终判交给语义评审。
- **不用自建哈希清单做历史留痕**：改用 git 做单一真源。

## 九、开发者：跑回归

改了这个工具本身之后，跑回归套件（纯标准库、离线；断言数看输出，本文件不写死）：

```bash
python -m tests.run_all                # 全部套件
python -m tests.run_all --dogfood      # 额外把仓库里归档的旧技能拉进来跑 dogfood 项
python -m tests.run_all --install      # 连打包的真装检查一起跑（需要网络，约 1 分钟）
python -m tests.test_lint              # 也可以单独跑某一套
```

套件共 12 个（默认跑 13，`--install` 加跑打包）：`spec` / `lint` / `discover` / `automation` / `evalx` / `trigger` / `review` / `docs` / `injection` / `selfcheck`（另有 `packaging`，默认不跑；全套约 1.5 分钟）。

其中 `injection` 是**注入自测**：先造一份 spec + lint + 评测 + 触发资产全绿的技能，再对它的**独立副本**逐个注入 38 处缺陷（含两处库级：评审记录过期、同名技能跨根），要求「期望的规则里至少一条必须报错」且「不许牵连无关规则」。
它证明的是**校验器不是瞎的**——方向与其它套件相反：那些查「给定坏输入会不会报错」，这个查「本来全绿的技能被动过手脚后一定会被抓住」。

打包相关的两档：默认只做 `pyproject.toml` 的静态检查（入口点/包列表/`package-data` glob
是否真的匹配到 `data/` 下的运行时数据）；加 `--install` 才真的建 venv、`pip install -e .`、
**在仓库之外的任意目录**执行命令，并构建 wheel 检查 `data/` 确实进了发布物。

> 改动交付文档里的命令时，`tests.test_docs` 会执行《验证流程指南.md》里
> `<!-- runnable -->` 标记的那段演练并要求退出码与标注一致——**文档承诺的东西必须真能跑**。

## 十、排障

| 现象 | 原因与处置 |
|---|---|
| `check` 说"未发现任何技能"并返回 1 | 根列表不对。`discover --show-config` 看生效配置；或用 `--root <技能根>` 直接指定 |
| 配置报"未知键" | 键名拼错了，报错信息里列了允许的键 |
| 退出码恒为 `2` | 通常是 SKIP（未执行项）：看报告里 `[可选批次·未覆盖]` 标记与 SKIP 行的说明 |
| 报告里出现 `DISC-001` | 同名技能出现在多个根，需你决定哪个是真源 |
| 报告里出现 `LIB-001`（WARN） | 库内元数据总量超出预算：宿主可能静默丢弃排在后面的技能。精简描述、合并同类技能，或用 `--metadata-budget` 按宿主实测值调整 |
| 报告里出现 `LIB-002`（WARN） | 两个技能的描述高度重叠，触发时可能互相抢。让每个描述点明本技能独有的能力与边界；确实同类就合并 |
| 审计单说「内容与上次审计不同」 | 技能内容在你审计之后变过。重新过一遍；若你没换过它，就要查是谁换的 |
| 报告里出现 `DISC-004` | 技能主文件名大小写不对（写成了 `Skill.md` 之类）。**大小写敏感的系统上它等于不存在**：宿主不加载、报错也很难懂。重命名为 `SKILL.md` |
| 工具说「未发现技能」，但我明明放进去了 | 先看 `SKILL.md` 的**文件名与位置**：名字必须精确是 `SKILL.md`（或 `skill.md`），且直接位于技能根下的 `<技能名>/` 里 |
| `deliver` 说"评审记录已过期" | 技能在评审后改过；重跑 `review pack` → 评审 → `collect` |
| Windows 终端乱码/崩溃 | 本项目强制 UTF-8；命令行入口已把 stdout/stderr 切到 UTF-8，无需额外设置 |
| `hook` 装不上 | `--project` 必须指向 git 仓库；`hook status` 可先看现状 |
| `mount --host` 敲错档名 | 应报「未知宿主档 'x'；可用: …」并退出 1。**若看到的是 traceback**，那是 `mount.py` 漏导入 `ConfigError` 的老缺陷（已修；防复发由 `test_selfcheck` 的「未定义名」守卫看着） |
| 报告说「检测到旧落点」 | 旧体系把触发查询集放在 `.verification/trigger-eval/queryset.json`；迁到 `evals/trigger-queryset.json`（本项目只认单一来源，不回退读旧路径） |
| `evals` 返回 2 但官方资产全绿 | 是 `TRIG-000` 在提示「没有触发评测证据」；补 `evals/trigger-queryset.json` + `trigger-runs.json` 即可 |

## 十一、命令与旗标速查（改结果的开关，一个都不藏）

`skillverify <命令> --help` 永远是权威。下表收的是**正文没有逐条讲**、但会改变结果的开关——
放在这里，免得出现"实现了却没人知道"。`tests/test_consistency.py` 会核对：
每个子命令、每个旗标**至少在某份根文档里出现过一次**（含本表）。

| 旗标 | 属于 | 作用 |
|---|---|---|
| `--iteration N` | `evals` / `check` / `deliver` | 只看指定那一轮 `iteration-N`（工作区有多轮时用）；指定的号不存在记 **WARN**，不静默跳过 |
| `--workspace 目录` | `evals` / `review material` | 显式指定评测工作区（默认找并列的 `<技能名>-workspace/`） |
| `--scripts` | `lint` / `audit` / `check` / `deliver` | 开启**脚本契约实测**（`--help` 可用性、非法参数退出码、幂等、输出量）。默认不开：执行技能自带脚本有风险，由你显式打开；`deliver --strict` 要过含脚本的技能必须带上它（此前 deliver 漏定义这个旗标，"严格模式"对含脚本技能永不可能通过） |
| `--script-timeout 秒` | `lint` / `check` / `watch` / `deliver` | 单个脚本实测的超时（默认 **10s**；另有 120s **全局预算**，用尽即记 SKIP） |
| `--run-timeout 秒` | `evals --run-with` | 委托执行的那条命令的整体超时（默认 **1800s**） |
| `--timeout 秒` | `review run --runner` | 每条提示词喂给执行器命令的超时（默认 **180s**） |
| `--desc-overlap 比例` | `discover` / `check` | 库级「描述词面重叠」的提示阈值（默认 **0.5**）；与 `--metadata-budget` 同属库级口径 |
| `--trace` | `check` | 把这次的 `check.md` + `check.json` 落到中央留痕目录（默认只打印） |
| `--no-record` | `deliver` | 只判定、不写交付记录（CI 里只想拿退出码时用） |
| `--no-store` | `review collect` | 只校验回写、不写中央评审记录 |
| `--no-config` | `audit` / `mount` | 忽略用户级/项目级 `hosts.toml`，只用内置档（排查"是不是我的配置在捣乱"） |
| `--fail-closed` | `hook install` | 找不到可执行的 `skillverify` 时**阻断**提交（默认**故障开放**，等价环境变量 `SKILLVERIFY_HOOK_STRICT=1`） |
| `--interval 秒` | `watch` | 轮询间隔（默认 **1s**） |
| `--cycles N` / `--once` | `watch` | 跑 N 轮后退出；`--once` 等价 `--cycles 1`（CI 与测试用） |
| `--blind-seed N` | `review material --blind` | 固定盲评 A/B 的随机分配（同一份盲评包可复现） |
| `--name 名` | `review skill` | 生成"语义评审"独立技能包时的技能名（默认 `skillverify-review`） |
| `--quiet` | 多数命令 | 不打印报告正文。**机读场景不要加**：它会把 `--json` 一起静音 |
| `--json` / `--out 文件` | 多数命令 | 机读输出 / 报告落盘；**两者可同时用**（stdout 仍只有 JSON，进度提示一律走 stderr） |

