diff --git a/.agents/skills/wavebench-docs/SKILL.md b/.agents/skills/wavebench-docs/SKILL.md
new file mode 100644
index 00000000..d7bcdc7c
--- /dev/null
+++ b/.agents/skills/wavebench-docs/SKILL.md
@@ -0,0 +1,106 @@
+---
+name: wavebench-docs
+description: >-
+ Develop and maintain the WaveBench repository documentation system. Use only
+ for active documentation development: audits, information-architecture
+ migrations, writing or rewriting project docs, generated references, and
+ documentation-focused diff reviews. Documentation must be a primary requested
+ deliverable; do not use for incidental doc edits during ordinary code changes,
+ merely reading docs, instrument operation, or unrelated Chinese copyediting.
+license: MIT
+metadata:
+ author: "WaveBench maintainers"
+ version: "1.0.0"
+ project: "wavebench"
+---
+
+# WaveBench documentation development
+
+## Boundary
+
+Use this skill only while developing or reviewing documentation owned by the
+WaveBench repository. It governs information architecture, page responsibility,
+canonical sources, migrations, navigation, examples, and documentation CI.
+
+Do not use it for:
+
+- normal WaveBench operation, diagnosis, measurement, or hardware control;
+- answering a question by reading existing docs without changing or auditing them;
+- ordinary code changes with only an incidental one-line documentation update;
+- generic Markdown editing or Chinese copyediting outside WaveBench.
+
+Hardware access is never part of a documentation audit. Runtime commands used to
+verify help or schema must be offline. If a documentation example requires real
+hardware, validate it statically unless the user separately authorizes the live
+operation under the `wavebench` safety workflow.
+
+## Start from repository facts
+
+1. Work from the Git repository root and inspect `git status --short --branch`.
+2. Read `README.md`, `pyproject.toml`, `CHANGELOG.md`, the relevant documentation
+ indexes, and the pages directly in scope.
+3. Resolve changing claims from implementation, executable help/schema, tests,
+ descriptors, and release tags. Existing prose is evidence to audit, not proof
+ of current behavior.
+4. Classify each page by audience, reader goal, type, canonical facts, and related
+ pages before editing it.
+5. Preserve unrelated changes and do not move or rewrite broad document sets
+ without an accepted audit and migration slice.
+
+Read [文档宪法](references/information-architecture.md) when deciding taxonomy,
+page contracts, sources of truth, Core/plugin ownership, README scope, status,
+lifecycle, or user journeys. This is the single normative source for those rules.
+
+## Choose one mode
+
+| Mode | Use when | Load |
+| --- | --- | --- |
+| `audit` | Assess a documentation set without broad edits | [audit.md](references/audit.md) |
+| `migrate` | Apply an accepted audit in small, traceable slices | [migrate.md](references/migrate.md) |
+| `write` | Add or substantially rewrite a specific page | [write.md](references/write.md) |
+| `review` | Review a documentation PR or diff | [review.md](references/review.md) |
+
+Load only the selected mode plus `information-architecture.md` when that mode
+needs constitutional rules. Do not turn every review into a repository-wide audit.
+
+## Invariants
+
+- Follow the document constitution for one-fact/one-source, status labels,
+ Core/plugin ownership and lifecycle actions.
+- Structure and facts come before prose polish. For Chinese writing or review,
+ apply `tech-doc-style-chinese` only after page responsibility and sources are
+ settled. Do not assume that skill's unrelated `Project-Overrides.md` applies to
+ WaveBench.
+- Apply the [协作输出卫生](references/information-architecture.md#协作输出卫生)
+ rule to reader-facing content created or reviewed in this workflow. It is a
+ contextual write/review judgment, not a hook, text filter, or CI gate.
+- Prefer generated Reference when stable code or schema can produce it. Generated
+ output must name its source and have a drift check before it becomes canonical.
+
+## Mechanical audit
+
+In `audit` mode, run the dependency-free checker for the full requested scope:
+
+```bash
+python .agents/skills/wavebench-docs/scripts/audit_docs.py
+```
+
+For `write` or `review`, pass the changed page and its direct navigation entry as
+paths; use a full audit only for an approved migration or a demonstrated systemic
+problem.
+
+The script checks deterministic breakage and reports judgment-heavy concerns as
+warnings. It does not decide page type, migration action, user-journey quality, or
+whether content belongs in a Guide, Reference, or Concept.
+
+For trigger-boundary maintenance, read [eval-prompts.md](references/eval-prompts.md).
+
+## Handoff
+
+Treat the handoff as an execution and safety record under the
+[协作输出卫生](references/information-architecture.md#协作输出卫生) rule. Report the
+documentation scope, canonical sources checked, files changed, audit errors and
+warnings, commands or examples verified, content intentionally deferred, and whether
+any live hardware, local configuration, generated data, or virtual environment was
+touched. Omit feedback back-and-forth and discarded alternatives unless they affect
+a remaining decision or risk.
diff --git a/.agents/skills/wavebench-docs/agents/openai.yaml b/.agents/skills/wavebench-docs/agents/openai.yaml
new file mode 100644
index 00000000..44a95db6
--- /dev/null
+++ b/.agents/skills/wavebench-docs/agents/openai.yaml
@@ -0,0 +1,6 @@
+interface:
+ display_name: "WaveBench Docs"
+ short_description: "审计、迁移、编写与评审 WaveBench 仓库文档"
+ default_prompt: "使用 $wavebench-docs 审计本次 WaveBench 文档改动,并核对事实源、导航和验证结果。"
+policy:
+ allow_implicit_invocation: true
diff --git a/.agents/skills/wavebench-docs/references/audit.md b/.agents/skills/wavebench-docs/references/audit.md
new file mode 100644
index 00000000..98ac8093
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/audit.md
@@ -0,0 +1,48 @@
+# Audit 模式
+
+> 加载时机:评估整个文档系统、一个目录或一组相关页面,且当前阶段不进行广泛迁移时加载。
+
+## 范围
+
+全仓 audit 至少盘点 `README.md`、`docs/`、`plans/README.md`、`CHANGELOG.md`、相关 CLI/schema/config/artifact/capability/safety 源码入口,以及 `.agents/skills/`。局部 audit 只扩展到修改页的导航入口、直接相关页面和 canonical source。
+
+先运行:
+
+```bash
+python .agents/skills/wavebench-docs/scripts/audit_docs.py
+```
+
+机械结果只是审计输入。不要把「链接没坏」写成「文档结构合理」。
+
+## Inventory
+
+每篇页面先按[文档宪法](information-architecture.md)确定主类别、读者目标、canonical
+source 和一个主要动作:
+
+| File | Type | Audience | Reader Goal | Canonical? | Problems | Action |
+| --- | --- | --- | --- | --- | --- | --- |
+
+`Canonical?` 说明页面是否为该事实的权威来源;若不是,写出实际来源。`Problems`
+优先记录职责混合、重复事实、过时风险、旅程断点、Core/plugin 越界和
+Current/RFC 混杂,不以措辞偏好充数。生命周期动作的定义只以文档宪法为准。
+
+## 系统级输出
+
+除 inventory 外,列出并排序:
+
+- 最严重的 10 个系统问题;
+- 八条用户旅程的断点;
+- 重复维护的易变事实;
+- 孤儿页面和失效导航;
+- 超长 mixed-purpose 页面;
+- Core/plugin 边界违规;
+- Current、Experimental、RFC 和历史记录的混杂;
+- 目标信息架构、事实源表和小步迁移顺序。
+
+结论必须引用 `file:line`、符号名、命令输出或 tag。若只是推断,要明确标注,不把旧文档互相引用当作事实核验。
+
+审计报告属于需要追溯的内容。为 Action 保留必要证据、影响、目标页或替代入口和验证结果;不记录讨论时间线或与结论无关的备选方案。
+
+## 当前基线
+
+首轮仓库审计见 [WaveBench 文档系统审计与迁移提案](../../../../docs/project/design/WaveBench_文档系统审计与迁移提案.md)。后续 audit 应重新扫描当前树,不得把这份日期快照当永久事实源。
diff --git a/.agents/skills/wavebench-docs/references/eval-prompts.md b/.agents/skills/wavebench-docs/references/eval-prompts.md
new file mode 100644
index 00000000..56e23389
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/eval-prompts.md
@@ -0,0 +1,35 @@
+# 触发与模式回归用例
+
+> 加载时机:维护 `wavebench-docs` 的 description、隐式触发边界或 reference 路由时加载。
+
+## 应触发
+
+| 请求 | Mode | 预期加载 |
+| --- | --- | --- |
+| 「审计 WaveBench 的 README、docs 与事实源,给迁移表」 | `audit` | 入口、`audit.md`、`information-architecture.md` |
+| 「按已批准的文档审计拆分 run plan 指南」 | `migrate` | 入口、`migrate.md`、`information-architecture.md` |
+| 「为新的 run step 编写 WaveBench Reference」 | `write` | 入口、`write.md`、`information-architecture.md` |
+| 「评审这个只改文档的 PR」 | `review` | 入口、`review.md`,按需加载信息架构 |
+
+## 不应触发
+
+| 请求 | 正确处理 |
+| --- | --- |
+| 「执行这个 WaveBench run plan」 | 使用 `wavebench` 的 run/safety 工作流 |
+| 「采集示波器 CH1 并生成报告」 | 使用 `wavebench`,先过硬件写入门禁 |
+| 「README 里怎么安装?」 | 直接读取并回答,不进入文档开发 workflow |
+| 「修复 config parser 的 bug,顺手更新一句报错说明」 | 普通 WaveBench 代码开发;附带的一行说明不触发完整文档工作流 |
+| 「把这段中文写自然一点」 | 使用 `tech-doc-style-chinese`,除非明确是 WaveBench 文档开发 |
+| 「解释 Diátaxis」 | 直接解释方法,不审计 WaveBench 仓库 |
+| 「解释傅里叶变换」 | 普通知识任务 |
+
+## 行为检查
+
+用全新上下文逐条测试,确认:
+
+1. 普通使用和只读问答不会加载本 Skill;
+2. 文档开发能自动选择正确 mode;
+3. `review` 不默认扩大为全仓 audit;
+4. `write` 在结构和事实确定前不会先做措辞润色;
+5. 文档任务不会自行连接仪器或执行实时 plan;
+6. 中文写作层按需交给 `tech-doc-style-chinese`,且不套用无关项目覆盖规则。
diff --git a/.agents/skills/wavebench-docs/references/information-architecture.md b/.agents/skills/wavebench-docs/references/information-architecture.md
new file mode 100644
index 00000000..8aaba514
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/information-architecture.md
@@ -0,0 +1,177 @@
+# WaveBench 文档宪法
+
+> 加载时机:任何 `audit`、`migrate`、`write` 或 `review` 任务中,需要判断页面职责、事实来源、状态、生命周期、导航或 Core/plugin 边界时加载。
+> 本文件是 WaveBench 文档分类、来源与治理规则的唯一规范入口;其它 workflow reference 不重复定义这些规则。
+
+## 适用范围与优先级
+
+本规则适用于仓库的公开文档、示例说明、README、Reference、开发文档和 RFC。它不改变代码、CLI、schema、descriptor 或发布流程本身;发生冲突时,这些可执行或已发布事实优先于文档。
+
+文档事实的核验顺序为:
+
+1. 实现、类型模型与 schema;
+2. 离线可执行的 `--help`、`run schema`、`run template --list` 和 capability 查询;
+3. 聚焦测试、descriptor 与 registry;
+4. 已发布 Git tag、release 与 `CHANGELOG.md`;
+5. 现有文档。
+
+旧页面只能作为待核验材料,不能互相证明当前行为。
+
+## 页面类别
+
+每篇公开页面只选择一个主类别。页面可以链接到其它类别,但不能借「概览」之名承载所有内容。
+
+| 类别 | Audience | Reader goal | 页面职责与允许内容 | 必须链接到其它位置 |
+| --- | --- | --- | --- | --- |
+| README / Landing | 首次访问者、评估者、贡献者 | 判断项目是否适用,并进入正确起点 | 项目身份、用途、核心能力、无硬件起步摘要、简短支持摘要、安全提醒、主要入口、贡献与许可证 | 安装详情、完整参数、型号状态、RFC、实现细节、长故障排查 |
+| Getting Started | 新环境使用者、首次接入实验台的人员 | 安装、配置或完成首个明确结果 | 最小前置条件、线性步骤、每个关键步骤的预期结果、成功判据、下一步 | 完整 schema、所有选项、架构原因、型号私有信息 |
+| Tutorial | 正在学习 WaveBench 工作方式的人员 | 在指导下完成一次完整体验并建立直觉 | 单条端到端学习路径、必要解释、可观察结果、最小安全边界 | 参数全集、所有替代路径、深层设计论证 |
+| How-to | 已知目标的操作者 | 可靠完成一个具体任务 | 任务目标、必要条件、硬件风险、最短步骤、Verification、常见失败、相关 Reference | 从零教学、完整 API/schema、开发历史 |
+| Reference | 查询精确行为的用户、自动化调用方、插件作者 | 查到准确、完整、可定位的机器事实 | Synopsis、syntax/schema、inputs、outputs、exact behavior、side effects、errors、compatibility;优先生成或自动验证 | 教程叙事、设计取舍、路线图、实验过程记录 |
+| Concept | 需要理解模型与取舍的用户、维护者 | 理解系统为何这样设计 | Problem、model、how it works、rationale、trade-offs、稳定边界 | 命令手册、字段全集、发布状态流水账、型号实机证据 |
+| Development | Core 或插件开发者 | 修改、测试、发布或迁移实现 | 开发流程、贡献边界、测试门、兼容策略、链接到 API Reference 和 RFC | 当前用户操作指南、型号能力矩阵、私有实验细节 |
+| RFC | 设计评审者、实现者 | 审阅提案、冻结合同或理解未发布设计 | 问题、约束、兼容性、决策、验收门和明确状态 | 面向用户的当前功能承诺;未发布实现不得伪装为稳定 Reference |
+| Historical | 维护者、追溯者 | 查询旧提案、里程碑、证据或发布时状态 | 日期/版本范围、原始背景、替代页面、不能作为当前事实源的声明 | 当前 CLI、实时支持状态、新的用户路径 |
+
+`Navigation` 是页面的辅助职责,不是独立事实源。导航页只说明阅读目的和下一步,不复制参数、profile、RFC 进度或 schema。
+
+## 页面合同
+
+所有主要页面先在工作笔记中确定:`type`、`audience`、`reader goal`、`canonical facts` 和 `related pages`。不要求把这五项机械写成公开页面的元数据块。
+
+### Tutorial
+
+- Learning goal;
+- Prerequisites;
+- 一条可完成的流程;
+- 每个关键步骤的预期结果;
+- 后续阅读入口。
+
+深层原因链接 Concept,参数全集链接 Reference。
+
+### How-to
+
+- 任务目标;
+- 必要条件和真实硬件风险;
+- 最短可靠步骤;
+- Verification;
+- 常见失败;
+- 相关 Reference。
+
+不把单个任务页扩展成产品总教程。
+
+### Reference
+
+- Synopsis;
+- Syntax / Schema;
+- Inputs / Fields / Parameters;
+- Outputs;
+- Exact behavior 和 side effects;
+- Errors;
+- Compatibility / capability requirements。
+
+可以从稳定实现生成的字段、参数或 capability 表,不长期手工复制。生成结果必须标明来源和生成/验证命令。
+
+### Concept
+
+- Problem / motivation;
+- Model;
+- How it works;
+- Design rationale;
+- Trade-offs;
+- 相关 Guide 与 Reference。
+
+不把命令步骤、当前版本号、commit、里程碑或型号证据塞进概念页。
+
+## One fact, one canonical source
+
+| 事实 | Canonical source | 文档规则 |
+| --- | --- | --- |
+| 当前包版本 | `pyproject.toml` 的 `[project].version` | 展示时核对,不在多个页面长期手写 |
+| 正式发布版本 | Git tag / release | 只有 tag/release 才能表示正式可用版本 |
+| 版本变化 | `CHANGELOG.md`,以对应 tag 和发布提交约束 | 开发分支进度不得伪装成发布历史 |
+| CLI 命令、参数和默认值 | CLI 实现和 `wavebench --help` | 参考页生成或验证;Guide 只保留任务所需命令 |
+| run plan step、字段和 schema | `run_plan.py` 和 `wavebench run schema`;派生页由 `scripts/generate_docs.py` 写入 `docs/reference/generated/run-schema.md` | 生成页必须以 `python scripts/generate_docs.py --check` 进入 CI;Guide 不复制完整 step 表 |
+| run template | 模板 registry 和 `wavebench run template --list` | 只在 Tutorial/How-to 选择适用示例 |
+| capability | descriptor、capability/operation registry 与 `capability explain` | 解释模型可以人工维护;精确可用性应查询或生成 |
+| 仪器支持状态 | Core 内建 descriptor 与 instrument plugin descriptor | Core 只给简短摘要;型号详情链接插件仓 |
+| 配置字段和约束 | config model、parser 和 schema | `wavebench.example.toml` 是示例,不是完整 schema |
+| artifact 格式 | artifact writer、typed result、package loader | 按 artifact 家族拆页;机器字段优先生成 |
+| 安全语义 | Core safety/service contract 与测试 | 说明风险和流程;不得从旧文档复制限制 |
+| 厂商 SCPI、型号参数、quirk、profile、实机验证 | `wavebench-instrument-plugins` 的 descriptor、实现、测试和证据 | Core 文档不维护完整型号矩阵 |
+| 未来设计 | RFC、issue 或 roadmap | 不写成 Current;用户页只说明 `unavailable` 或 `unsupported` |
+
+Guide 可以解释或摘要 canonical fact,但不得变成第二份完整、易变的手工表。事实无法核验时,必须标为未验证,不以相邻文档补齐。
+
+## Repository 边界
+
+| 归属 | 文档责任 |
+| --- | --- |
+| WaveBench Core | 通用仪器抽象、CLI、run plan、artifact、安全模型、capability 模型、插件 API、通用配置、session 和 recovery |
+| `wavebench-instrument-plugins` | 具体型号、厂商 SCPI、私有参数、quirk、型号 profile/capability、型号限制和实机验证状态 |
+| 两仓交界 | Core 定义通用合同与查询入口;插件仓声明实际型号采用和证据。Core 可提供短支持摘要,但不能重复整张型号 capability 矩阵 |
+
+型号名本身不是越界理由。只有当页面维护型号私有命令、范围、profile 或 evidence 时,内容才应回到插件仓。
+
+## README policy
+
+README 是 landing page,不是 specification。它应包含:项目身份和一句定位、主要入口、WaveBench 解决的问题、核心能力、无硬件 Quickstart 摘要、简短支持概览、安全提醒、Contributing、License 和 Acknowledgements。
+
+README 不应承载:RFC 编号、milestone、A1/A2/A4 阶段、descriptor 内部合同、session/rollback 实现、型号级 capability/profile、易变化的型号参数或完整错误/配置/CLI Reference。需要这些信息时,用一两句摘要链接到唯一权威页面。
+
+## 生命周期
+
+每篇现有页面在 audit 或迁移计划中只选择一个主要动作:
+
+| Action | 适用条件 |
+| --- | --- |
+| `KEEP` | 职责清楚、事实来源稳定、导航可达 |
+| `REWRITE` | 路径和主职责保留,但正文无法通过小修恢复单一读者结果 |
+| `SPLIT` | 同页服务多个独立读者目标或混合多个页面类别 |
+| `MERGE` | 两页重复维护同一职责或同一易变事实 |
+| `MOVE` | 内容职责正确,但目录/类别表达错误 |
+| `GENERATE` | 机器事实可由稳定模型、schema、registry 或 help 生成或自动验证 |
+| `ARCHIVE` | 历史仍有追溯价值,但不能作为当前事实源 |
+| `DELETE` | 没有独立价值,且已被 canonical 页面完整替代 |
+
+不因页面很长就自动 `SPLIT`,不因目录不整齐就自动 `MOVE`,也不为对称性创建空页面。每次 `SPLIT` 或 `MOVE` 都要有目标读者、目标页面、链接处理和内容保全方案。
+
+## 状态 policy
+
+| 状态 | 定义 | 可出现的位置 |
+| --- | --- | --- |
+| Current | 已发布,并由当前实现、测试和文档共同支持 | Guide、Reference、Concept、README 摘要 |
+| Experimental | 已可用,但稳定性、兼容性或支持范围未承诺;必须显式标注 | Guide、Reference、Concept |
+| Proposed / Future | 未实现、未发布或仅候选设计 | RFC、issue、roadmap;不得作为用户操作承诺 |
+| Historical | 旧提案、里程碑、证据、旧发行物或替代前合同 | RFC/archive/对应 Git tag;页面顶部说明替代入口 |
+
+「开发线已实现但未发布」对用户文档属于 `Proposed / Future`,不是 `Current`。它可以在 RFC 或 Development 文档中作为实施状态出现,但必须明确不能据此宣称稳定能力、提高插件版本门或执行未经授权的硬件操作。
+
+## 协作输出卫生
+
+本条适用于本工作流撰写或审查的面向读者的当前文档、README、导航文案,以及只需说明最终结果的读者交接摘要。用户纠正、被否决的备选方案和内部协作过程应改变结果,但通常不是正文内容。直接说明当前对象、行为、限制或下一步,不用「根据反馈」「已移除」「不再包含」等对比式表述来纪念被放弃的内容。
+
+兼容旧路径的短页应直接给出当前入口,可以说明该路径用于兼容旧链接;除非追溯用途需要,不展开迁移时间线。
+
+任务执行和安全交接不属于上述省略范围。交接必须报告后续操作所需的范围、事实源、验证命令与结果、失败或跳过项、未验证内容、剩余风险和环境状态;只省略无关的反馈往返与已弃选项。
+
+需要追溯事实时,不适用上述省略原则:RFC、archive、CHANGELOG、审计或迁移报告、事故记录,以及会影响读者决定的安全、合规或限制说明,可以记录变更原因、证据和影响。保留内容应至少说明来源、时间范围、影响、替代入口或可核验证据之一,并服务于页面职责或读者决策;页面位于追溯目录不等于其中所有过程叙述都应保留。不满足该判据的内容应压缩为当前事实。
+
+这是一项写作和 review 判断,不通过 hook、关键词过滤或 CI 自动阻断实现。机械检查仍只处理确定性问题;是否应保留追溯信息由页面职责、读者目标和事实来源决定。
+
+## 导航与迁移护栏
+
+目标分类可以逐步采用 `getting-started/`、`tutorials/`、`how-to/`、`reference/`、`concepts/`、`development/`、`rfcs/` 和 `archive/`,但目录不是迁移的前置条件。只有明确读者目标和内容来源时才创建页面。
+
+所有文档系统都应支持以下阅读路径:
+
+1. README → Quickstart → 离线或 fake 的可见结果;
+2. Installation → Configure Bench → `doctor` → `run verify`;
+3. Tutorial / How-to → `run plan` → artifacts → report;
+4. error → Troubleshooting → Error Reference;
+5. navigation/search → 精确 Reference;
+6. Concepts → 设计取舍;
+7. Development → Driver/Plugin guide → API Reference;
+8. RFC / CHANGELOG → 未来设计或发布历史。
+
+迁移先建立目标页面,再转移一类职责,更新入链/出链并验证,最后才归档或删除旧页面。日常 review 只检查本次 diff、直接相关页面和 canonical source;只有用户明确要求或发现系统性问题时才升级为全仓 audit。
diff --git a/.agents/skills/wavebench-docs/references/migrate.md b/.agents/skills/wavebench-docs/references/migrate.md
new file mode 100644
index 00000000..d9b33204
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/migrate.md
@@ -0,0 +1,50 @@
+# Migrate 模式
+
+> 加载时机:已有被接受的 audit 或明确迁移决策,需要移动、拆分、合并、归档或生成文档时加载。
+
+## 迁移门槛
+
+迁移前确认:
+
+- 已加载[文档宪法](information-architecture.md),并按其页面类别、状态与生命周期规则完成判断;
+- 页面主类型、受众和 reader outcome 已确定;
+- 每项易变事实已有 canonical source;
+- 旧页面的 `KEEP/REWRITE/SPLIT/MERGE/MOVE/GENERATE/ARCHIVE/DELETE` 已决定;
+- 入口、入链、出链和外部可见 URL 已盘点;
+- 本轮切片有可观察的完成标准。
+
+没有这些条件时退回 audit,不靠边移动文件边猜信息架构。
+
+## 小步顺序
+
+1. 先建立目标页或生成器,保留旧入口。
+2. 搬运一类职责,核对事实与技术内容没有丢失。
+3. 更新直接导航、交叉链接和代码示例引用。
+4. 运行文档 audit、相关 `--help`/schema 检查和聚焦测试。
+5. 再将旧页改为短跳转、归档或删除。
+
+一轮优先处理 2~4 个能验证设计的页面。通常顺序为 README、docs index、Quickstart、最大 mixed-purpose guide;不要一次性重写整个 `docs/`。
+
+## 链接与历史
+
+- GitHub Markdown 没有真正重定向;移动高入链页面时,优先保留短的旧路径说明,直到外部入口已迁移。
+- 兼容页的 H1 使用「旧入口」标识,正文直接给出当前入口,可以说明该路径仅用于兼容旧链接,但不展开迁移时间线。机械 audit 不把这种仅为外部书签保留的页面报为孤儿。
+- RFC 的不可变决策与实现历史分开。不要为了目录整齐改写已经接受的历史裁决含义。
+- `CHANGELOG.md` 只记录正式 tag;迁移说明不能把开发分支写成发布版本。
+- 归档页面顶部标明历史范围、替代页面和不可作为当前事实源的边界。
+- 迁移报告保留 Action 的必要证据、影响、目标页或替代入口和验证结果,不记录讨论时间线或无关备选方案。
+
+## 生成式 Reference
+
+先选择稳定、离线、确定性的源。生成结果必须记录生成命令、源文件或 schema 标识,并由 CI 检查漂移。不要在首轮同时发明生成框架、文档站和版本系统;先从 `run schema` 或 CLI help 的一个窄 Reference 证明流程。
+
+## 验证
+
+至少执行:
+
+```bash
+python .agents/skills/wavebench-docs/scripts/audit_docs.py
+git diff --check
+```
+
+再按改动增加离线 CLI、schema、示例 plan 或聚焦测试。除非另有明确授权,迁移文档不连接真实仪器,不覆盖 `wavebench.toml`,不生成或提交真实实验数据。
diff --git a/.agents/skills/wavebench-docs/references/review.md b/.agents/skills/wavebench-docs/references/review.md
new file mode 100644
index 00000000..f6cc3a58
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/review.md
@@ -0,0 +1,43 @@
+# Review 模式
+
+> 加载时机:评审文档 PR、提交或工作区 diff 时加载。默认不是全仓 audit。
+
+## Review scope
+
+优先检查:
+
+1. 本次修改的页面;
+2. 它们直接依赖的 canonical Reference 或源码;
+3. 导航入口与入链页面;
+4. 被引用的命令、配置、plan、schema 和 artifact;
+5. 与改动声明直接相关的测试或 descriptor。
+
+只有发现系统性重复、迁移跨目录或用户明确要求时,才升级为全仓 audit。
+
+## Review order
+
+- 先按[文档宪法](information-architecture.md)确认页面类别、状态、事实源、边界和 lifecycle;
+- 页面 type、audience 和 reader outcome 是否一致;
+- 当前行为、Experimental、RFC 与历史是否清楚分离;
+- 易变事实是否回到唯一 canonical source;
+- Core/plugin 边界是否正确;
+- 链接、导航、文件路径和锚点是否有效;
+- 命令、参数、schema、capability、artifact 和示例是否与实现一致;
+- 按[协作输出卫生](information-architecture.md#协作输出卫生)区分当前正文、兼容入口、追溯内容和执行交接;不要因否定措辞而删除当前行为、安全边界或必要的交接事实;
+- 中文页面最后使用 `tech-doc-style-chinese` 检查表达层。
+
+先读 diff。代码 diff 可先用 `scripts/docs_impact.py --base --head HEAD` 找到 canonical 页面候选;它只缩小 review 范围,不能决定页面是否确实需要改动。确认本次修改的 Markdown 后,再把这些路径显式传给机械 audit。例如:
+
+```bash
+python .agents/skills/wavebench-docs/scripts/audit_docs.py README.md docs/index.md
+git diff -- README.md docs plans .agents/skills
+git diff --check
+```
+
+脚本仍以完整文档图解析链接和入链,但只报告指定文件的问题。发现跨目录迁移或系统性重复时,再不带路径运行全量 audit。
+
+## Findings
+
+按会导致错误操作、错误事实、断链或长期漂移的风险排序。每条 finding 给出紧凑的 `file:line`、影响、事实依据和最小修正方向。
+
+不要把个人措辞偏好、未改动页面的旧问题或完整仓库愿望清单混进 PR review。没有实质问题时明确说明,并列出尚未验证的命令、外链或硬件行为。
diff --git a/.agents/skills/wavebench-docs/references/write.md b/.agents/skills/wavebench-docs/references/write.md
new file mode 100644
index 00000000..cfb651d8
--- /dev/null
+++ b/.agents/skills/wavebench-docs/references/write.md
@@ -0,0 +1,46 @@
+# Write 模式
+
+> 加载时机:新增页面,或按已确定职责重写一篇具体页面时加载。
+
+## 写前合同
+
+开始正文前回答:
+
+- 谁会来看?
+- 当前要完成什么?
+- 读完后能做到什么?
+- 哪些陈述来自哪些 canonical source?
+- 页面主类型是什么?相关细节应链接到哪里?
+
+无法回答时先做局部 audit。不要把一篇混合页面原样换个标题。
+
+## 按类型写作
+
+先加载[文档宪法](information-architecture.md)中的页面类别与页面合同。它是
+Tutorial、How-to、Reference 和 Concept 的唯一结构来源;本文件只处理写作时的
+WaveBench 核验,不重复页面类别规则。
+
+## WaveBench 特有检查
+
+- 示例命令先与当前 `--help`、`run schema` 或实现核对。
+- 涉及真实设备的步骤明确区分离线、连接读取和设备写入。
+- 不写入真实 IP、序列号、串口、凭据、本地实验目录或私有证据。
+- 型号级 SCPI、profile、quirk 和验证状态链接 instrument plugin 仓库。
+- 在文档宪法允许出现状态的位置,Current、Experimental、Proposed 和 Historical 使用明确标签。
+- README 只保留 landing page 内容,不展开内部合同。
+- 面向读者的当前页面遵守[协作输出卫生](information-architecture.md#协作输出卫生):写当前结果,不写纠正或删除过程;需要追溯的页面按宪法中的例外处理。
+
+## 中文表达层
+
+页面结构、事实源和技术边界确定后,再使用 `tech-doc-style-chinese` 完成中文措辞与排版:直角引号、克制语气、术语与中西文留白、机器字面量保护等由该 Skill 负责。
+
+不要复制那套通用规则到本 Skill,也不要加载其与 WaveBench 无关的 `Project-Overrides.md`。若 WaveBench 以后需要术语覆盖,应在仓库内建立并评审自己的规则。
+
+## 完成条件
+
+- reader outcome 可验证;
+- 事实均能追溯到 canonical source;
+- 示例与路径可执行或明确标注未执行原因;
+- 导航入口和 related pages 已更新;
+- 对单页 write,`audit_docs.py` 显式传入改动页及其直接导航入口,且无新增错误;只有已批准迁移或已证实的系统性问题才运行全量 audit;
+- 未验证、Experimental 或未来内容没有伪装成当前能力。
diff --git a/.agents/skills/wavebench-docs/scripts/audit_docs.py b/.agents/skills/wavebench-docs/scripts/audit_docs.py
new file mode 100644
index 00000000..1cae57db
--- /dev/null
+++ b/.agents/skills/wavebench-docs/scripts/audit_docs.py
@@ -0,0 +1,412 @@
+#!/usr/bin/env python3
+"""Audit deterministic documentation integrity without external dependencies.
+
+The checker deliberately leaves information architecture, page type, audience,
+and migration decisions to a human or agent review. Errors fail the command;
+warnings become failures only with ``--strict``.
+"""
+
+from __future__ import annotations
+
+import argparse
+import ipaddress
+import re
+import subprocess
+import sys
+import tomllib
+import unicodedata
+from collections import defaultdict
+from dataclasses import dataclass
+from pathlib import Path
+from urllib.parse import unquote, urlsplit
+
+
+DOCUMENT_ROOTS = ("docs", "plans", ".agents/skills")
+ROOT_DOCUMENTS = ("README.md", "CHANGELOG.md", "SKILL.md")
+EXCLUDED_PREFIXES = ("tool-of-rei/",)
+ENTRY_NAMES = {"README.md", "CHANGELOG.md", "SKILL.md"}
+LONG_PAGE_LINES = 600
+
+FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})")
+HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$")
+HTML_ID_RE = re.compile(r"<(?:a\s+name|[^>]+\sid)=[\"']([^\"']+)[\"']", re.IGNORECASE)
+LINK_RE = re.compile(
+ r"!?\[[^\]]*\]\(\s*(<[^>]+>|[^)\s]+)"
+ r"(?:\s+(?:\"[^\"]*\"|'[^']*'|\([^)]*\)))?\s*\)"
+)
+SEMVER_RE = re.compile(r"(? Path:
+ result = subprocess.run(
+ ["git", "-C", str(start), "rev-parse", "--show-toplevel"],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ return Path(result.stdout.strip()).resolve()
+
+
+def discover_markdown(root: Path) -> list[Path]:
+ paths: set[Path] = set()
+ for name in ROOT_DOCUMENTS:
+ candidate = root / name
+ if candidate.is_file():
+ paths.add(candidate)
+ for directory in DOCUMENT_ROOTS:
+ base = root / directory
+ if base.is_dir():
+ paths.update(path for path in base.rglob("*.md") if path.is_file())
+ return sorted(
+ path
+ for path in paths
+ if not path.relative_to(root).as_posix().startswith(EXCLUDED_PREFIXES)
+ )
+
+
+def visible_markdown_lines(lines: tuple[str, ...]) -> tuple[tuple[int, str], ...]:
+ visible: list[tuple[int, str]] = []
+ fence_char = ""
+ fence_length = 0
+ for number, line in enumerate(lines, 1):
+ match = FENCE_RE.match(line)
+ if fence_char:
+ if match and match.group(1)[0] == fence_char and len(match.group(1)) >= fence_length:
+ fence_char = ""
+ fence_length = 0
+ continue
+ if match:
+ fence_char = match.group(1)[0]
+ fence_length = len(match.group(1))
+ continue
+ visible.append((number, line))
+ return tuple(visible)
+
+
+def heading_slug(title: str) -> str:
+ text = re.sub(r"`([^`]*)`", r"\1", title.casefold())
+ text = re.sub(r"!?\[([^\]]+)\]\([^)]+\)", r"\1", text)
+ slug: list[str] = []
+ for character in text:
+ if character.isspace():
+ slug.append("-")
+ elif character in {"-", "_"} or not unicodedata.category(character).startswith(("P", "S")):
+ slug.append(character)
+ return re.sub(r"-+", "-", "".join(slug)).strip("-")
+
+
+def markdown_structure(
+ visible_lines: tuple[tuple[int, str], ...],
+) -> tuple[tuple[Heading, ...], frozenset[str]]:
+ headings: list[Heading] = []
+ anchors: set[str] = set()
+ seen_slugs: defaultdict[str, int] = defaultdict(int)
+ for number, line in visible_lines:
+ match = HEADING_RE.match(line)
+ if match:
+ title = match.group(2).strip()
+ headings.append(Heading(len(match.group(1)), title, number))
+ base = heading_slug(title)
+ suffix = seen_slugs[base]
+ anchors.add(base if suffix == 0 else f"{base}-{suffix}")
+ seen_slugs[base] += 1
+ anchors.update(unquote(value) for value in HTML_ID_RE.findall(line))
+ return tuple(headings), frozenset(anchors)
+
+
+def load_document(path: Path, root: Path) -> Document:
+ lines = tuple(path.read_text(encoding="utf-8").splitlines())
+ visible = visible_markdown_lines(lines)
+ headings, anchors = markdown_structure(visible)
+ return Document(
+ path=path,
+ relative=path.relative_to(root),
+ canonical=path.resolve(),
+ lines=lines,
+ visible_lines=visible,
+ headings=headings,
+ anchors=anchors,
+ )
+
+
+def markdown_links(document: Document) -> list[tuple[int, str]]:
+ links: list[tuple[int, str]] = []
+ for number, line in document.visible_lines:
+ links.extend((number, match.group(1).strip("<>")) for match in LINK_RE.finditer(line))
+ return links
+
+
+def _inside(candidate: Path, root: Path) -> bool:
+ try:
+ candidate.relative_to(root)
+ except ValueError:
+ return False
+ return True
+
+
+def _target_document(path: Path, documents: dict[Path, Document], root: Path) -> Document | None:
+ canonical = path.resolve()
+ if canonical in documents:
+ return documents[canonical]
+ if path.is_file() and path.suffix.lower() == ".md" and _inside(canonical, root):
+ return load_document(path, root)
+ return None
+
+
+def check_links(
+ documents: list[Document], root: Path
+) -> tuple[list[Finding], dict[Path, set[Path]]]:
+ findings: list[Finding] = []
+ inbound: dict[Path, set[Path]] = defaultdict(set)
+ by_canonical = {document.canonical: document for document in documents}
+
+ for document in documents:
+ base = document.canonical.parent if document.path.is_symlink() else document.path.parent
+ for line, destination in markdown_links(document):
+ parsed = urlsplit(destination)
+ if parsed.scheme or parsed.netloc:
+ continue
+ relative_target = unquote(parsed.path)
+ target = document.path if not relative_target else base / relative_target
+ target = target.resolve()
+ if not _inside(target, root):
+ findings.append(
+ Finding("error", document.relative.as_posix(), line, f"relative link escapes repository: {destination}")
+ )
+ continue
+ if not target.exists():
+ findings.append(
+ Finding("error", document.relative.as_posix(), line, f"relative link target does not exist: {destination}")
+ )
+ continue
+ if target.is_file() and target.suffix.lower() == ".md":
+ inbound[target].add(document.canonical)
+ if parsed.fragment:
+ target_document = _target_document(target, by_canonical, root)
+ anchor = unquote(parsed.fragment).casefold()
+ if target_document and anchor not in target_document.anchors:
+ findings.append(
+ Finding("warning", document.relative.as_posix(), line, f"anchor was not found: {destination}")
+ )
+ return findings, inbound
+
+
+def check_structure(
+ documents: list[Document], inbound: dict[Path, set[Path]], max_lines: int
+) -> list[Finding]:
+ findings: list[Finding] = []
+ canonical_documents: dict[Path, Document] = {}
+ for document in documents:
+ canonical_documents.setdefault(document.canonical, document)
+
+ titles: dict[str, list[Document]] = defaultdict(list)
+ for document in canonical_documents.values():
+ h1 = [heading for heading in document.headings if heading.level == 1]
+ if not h1:
+ findings.append(Finding("warning", document.relative.as_posix(), 1, "page has no ATX H1"))
+ elif len(h1) > 1:
+ findings.append(Finding("warning", document.relative.as_posix(), h1[1].line, "page has more than one H1"))
+ else:
+ titles[h1[0].title.casefold()].append(document)
+ if len(document.lines) > max_lines and document.relative.parts[:2] != ("docs", "archive"):
+ findings.append(
+ Finding("warning", document.relative.as_posix(), 1, f"long page: {len(document.lines)} lines (threshold {max_lines})")
+ )
+
+ is_entry = document.relative.name in ENTRY_NAMES or (
+ len(document.relative.parts) >= 2
+ and document.relative.parts[-1] == "SKILL.md"
+ and "skills" in document.relative.parts
+ )
+ is_compatibility_entry = any(
+ heading.level == 1 and "旧入口" in heading.title for heading in document.headings
+ )
+ if not is_entry and not is_compatibility_entry and not inbound.get(document.canonical):
+ findings.append(Finding("warning", document.relative.as_posix(), 1, "orphan Markdown page: no inbound Markdown link"))
+
+ for duplicate_title, matches in titles.items():
+ if len(matches) < 2:
+ continue
+ paths = ", ".join(item.relative.as_posix() for item in matches)
+ for document in matches:
+ h1 = next(heading for heading in document.headings if heading.level == 1)
+ findings.append(
+ Finding("warning", document.relative.as_posix(), h1.line, f"duplicate H1 {duplicate_title!r}: {paths}")
+ )
+ return findings
+
+
+def project_version(root: Path) -> str:
+ data = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
+ return str(data["project"]["version"])
+
+
+def check_version_drift(documents: list[Document], current_version: str) -> list[Finding]:
+ findings: list[Finding] = []
+ for document in documents:
+ relative = document.relative.as_posix()
+ if relative == "CHANGELOG.md" or relative.startswith("docs/project/rfcs/"):
+ continue
+ for number, line in document.visible_lines:
+ lowered = line.casefold()
+ current_claim = (
+ "当前开发线" in line
+ or "current development line" in lowered
+ or bool(re.search(r"(?:Core|核心)\s*`?v?\d+\.\d+\.\d+`?\s*开发线", line))
+ )
+ if not current_claim or any(marker in line for marker in ("静态下限", "只表示", "核心基线")):
+ continue
+ versions = set(SEMVER_RE.findall(line))
+ if versions and current_version not in versions:
+ findings.append(
+ Finding(
+ "warning",
+ relative,
+ number,
+ f"current-development version may have drifted: {sorted(versions)} != {current_version}",
+ )
+ )
+ return findings
+
+
+def _allowed_documentation_ip(value: str) -> bool:
+ try:
+ address = ipaddress.ip_address(value)
+ except ValueError:
+ return True
+ return address.is_loopback or address.is_unspecified or any(address in network for network in TEST_NETWORKS)
+
+
+def check_sensitive_resources(documents: list[Document]) -> list[Finding]:
+ findings: list[Finding] = []
+ for document in documents:
+ relative = document.relative.as_posix()
+ for number, line in enumerate(document.lines, 1):
+ for match in IPV4_RE.finditer(line):
+ if not _allowed_documentation_ip(match.group(0)):
+ findings.append(
+ Finding("warning", relative, number, f"review possible real network address: {match.group(0)}")
+ )
+ for match in VISA_RE.finditer(line):
+ resource = match.group(0)
+ if "<" in resource or "..." in resource:
+ continue
+ ips = IPV4_RE.findall(resource)
+ if not ips or any(not _allowed_documentation_ip(value) for value in ips):
+ findings.append(Finding("warning", relative, number, f"review possible real VISA resource: {resource}"))
+ if SERIAL_RE.search(line):
+ findings.append(Finding("warning", relative, number, "review possible device serial number"))
+ if HOME_PATH_RE.search(line):
+ findings.append(Finding("warning", relative, number, "review machine-specific absolute path"))
+ return findings
+
+
+def audit(root: Path, max_lines: int) -> tuple[list[Document], list[Finding]]:
+ documents = [load_document(path, root) for path in discover_markdown(root)]
+ link_findings, inbound = check_links(documents, root)
+ findings = [
+ *link_findings,
+ *check_structure(documents, inbound, max_lines),
+ *check_version_drift(documents, project_version(root)),
+ *check_sensitive_resources(documents),
+ ]
+ findings.sort(key=lambda item: (item.path.casefold(), item.line, item.level, item.message))
+ return documents, findings
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument(
+ "paths",
+ nargs="*",
+ type=Path,
+ help="report findings only for these repository-relative files or directories",
+ )
+ parser.add_argument("--root", type=Path, help="repository root; defaults to the current Git worktree")
+ parser.add_argument("--max-lines", type=int, default=LONG_PAGE_LINES, help="long-page warning threshold")
+ parser.add_argument("--strict", action="store_true", help="treat warnings as failures")
+ parser.add_argument("--quiet-warnings", action="store_true", help="hide warning details but retain their count")
+ args = parser.parse_args()
+
+ try:
+ root = args.root.resolve() if args.root else repository_root(Path.cwd())
+ documents, findings = audit(root, args.max_lines)
+ except (OSError, subprocess.CalledProcessError, KeyError, tomllib.TOMLDecodeError) as error:
+ print(f"ERROR: unable to audit documentation: {error}", file=sys.stderr)
+ return 2
+
+ selected: set[str] = set()
+ for requested in args.paths:
+ candidate = requested.resolve() if requested.is_absolute() else (root / requested).resolve()
+ if not _inside(candidate, root):
+ print(f"ERROR: requested path escapes repository: {requested}", file=sys.stderr)
+ return 2
+ relative = candidate.relative_to(root)
+ if candidate.is_dir():
+ prefix = relative.as_posix().rstrip("/") + "/"
+ selected.update(
+ document.relative.as_posix()
+ for document in documents
+ if document.relative.as_posix().startswith(prefix)
+ )
+ else:
+ selected.add(relative.as_posix())
+ if selected:
+ findings = [finding for finding in findings if finding.path in selected]
+
+ for finding in findings:
+ if args.quiet_warnings and finding.level == "warning":
+ continue
+ print(f"{finding.level.upper()}: {finding.path}:{finding.line}: {finding.message}")
+ errors = sum(item.level == "error" for item in findings)
+ warnings = sum(item.level == "warning" for item in findings)
+ canonical = len({document.canonical for document in documents})
+ print(
+ f"SUMMARY: {len(documents)} Markdown paths ({canonical} canonical), "
+ f"{errors} errors, {warnings} warnings"
+ + (f", scoped to {len(selected)} requested paths" if selected else "")
+ )
+ if errors or (args.strict and warnings):
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/.agents/skills/wavebench/references/development-validation.md b/.agents/skills/wavebench/references/development-validation.md
index aac8bed4..6936e312 100644
--- a/.agents/skills/wavebench/references/development-validation.md
+++ b/.agents/skills/wavebench/references/development-validation.md
@@ -40,6 +40,10 @@ git diff --check
## 文档规则
+涉及 WaveBench 文档体系审计、信息架构迁移、新增或重写页面以及文档 diff 评审时,使用仓库内
+`wavebench-docs` Skill;普通代码任务中只更新一两处直接相关说明时,不自动扩大为全仓文档审计。
+先确定页面职责和事实源,再处理中文表达。
+
中文 Markdown 使用 `tech-doc-style-chinese` 规则:正文使用直角引号「」,避免第二人称和宣传腔,中文与英文或数字之间留空格;代码、路径、URL、API 路径和配置键保持原样。修改后运行:
```bash
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 743dfcbd..b192c94f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
- name: Set up Python
uses: actions/setup-python@v5
@@ -32,6 +34,18 @@ jobs:
- name: Run Ruff
run: python -m ruff check .
+ - name: Audit documentation
+ if: matrix.python-version == '3.11'
+ run: python .agents/skills/wavebench-docs/scripts/audit_docs.py --quiet-warnings
+
+ - name: Check generated Reference
+ if: matrix.python-version == '3.11'
+ run: python scripts/generate_docs.py --check
+
+ - name: Report documentation impact
+ if: matrix.python-version == '3.11'
+ run: python scripts/docs_impact.py
+
- name: Run unit tests
run: python -m pytest -q
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000..a3cffc00
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,66 @@
+name: Documentation
+
+on:
+ push:
+ branches: [master, main]
+ paths:
+ - ".agents/skills/wavebench-docs/**"
+ - ".github/workflows/docs.yml"
+ - "CHANGELOG.md"
+ - "README.md"
+ - "docs/**"
+ - "mkdocs.yml"
+ - "plans/**"
+ - "pyproject.toml"
+ - "scripts/docs_impact.py"
+ - "scripts/generate_docs.py"
+ - "src/wavebench/**"
+ - "wavebench.example.toml"
+ pull_request:
+ paths:
+ - ".agents/skills/wavebench-docs/**"
+ - ".github/workflows/docs.yml"
+ - "CHANGELOG.md"
+ - "README.md"
+ - "docs/**"
+ - "mkdocs.yml"
+ - "plans/**"
+ - "pyproject.toml"
+ - "scripts/docs_impact.py"
+ - "scripts/generate_docs.py"
+ - "src/wavebench/**"
+ - "wavebench.example.toml"
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build documentation
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+ cache: pip
+
+ - name: Install documentation dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install -e ".[dev,docs]"
+
+ - name: Check generated Reference
+ run: python scripts/generate_docs.py --check
+
+ - name: Audit documentation
+ run: python .agents/skills/wavebench-docs/scripts/audit_docs.py --quiet-warnings
+
+ - name: Build site
+ run: mkdocs build --strict
+
+ - name: Report documentation impact
+ run: python scripts/docs_impact.py
diff --git a/.gitignore b/.gitignore
index 19f86668..e2f782aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ tool-of-rei/
/.agents/skills/*
!/.agents/skills/wavebench/
!/.agents/skills/wavebench/**
+!/.agents/skills/wavebench-docs/
+!/.agents/skills/wavebench-docs/**
.codex/
# Local WaveBench config and generated data
@@ -22,4 +24,5 @@ __pycache__/
.ruff_cache/
dist/
build/
+site/
*.egg-info/
diff --git a/README.md b/README.md
index 441853f0..ead3ec69 100644
--- a/README.md
+++ b/README.md
@@ -1,228 +1,114 @@
-# WaveBench
-
-[English documentation](docs/README_EN.md) · [中文文档总览](docs/README.md) · [更新日志](CHANGELOG.md) · [仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins)
-
-> [!WARNING]
-> WaveBench 可以连接并控制真实实验设备。执行会改动仪器状态的命令前,应确认接线、输入阻抗、输出状态和电压 / 电流限制。
-
-WaveBench 是一个用 Python 编写的实验室自动测量台,面向电子设计竞赛调试和日常实验。它把仪器控制、实验步骤和采集证据放在同一条命令链中,支持先离线检查 plan,再决定是否连接硬件。
-
-当前仓库开发线为 `0.8.26`,最新稳定 tag 为 `v0.8.0`。不同版本的命令和能力可能不同,以对应 tag 中的文档为准。
+
+
+
-## 🌟 特别鸣谢
+
+ Automate the bench. Measure what matters.
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-学AI,上L站!祝小破站越来越好~
-
-## WaveBench 适合做什么
-
-- 把信号源、示波器、电源和万用表组合成一条可复现的实验流程。
-- 保存 CSV、NPY、JSON metadata、命令记录和报告,方便复查结果。
-- 用明确的命令控制输出,不在后台隐式执行 reset、autoscale 或输出切换。
-- 在主包内使用常见仪器;需要其他型号时,再显式安装受信任的本地插件。
-
-### WaveBench 特色功能
-
-#### 测试报告
-
-`run report` 会读取已有的 `run.json`、采集包和命令记录,生成可离线查看的 HTML 报告。报告汇总运行状态、验收结果、波形与频响分析、警告、恢复状态和原始证据链接,适合复查一次实验到底发生了什么。
-
-
-
-#### 普通扫频
-
-普通扫频在固定 Vpp 下沿频率轴采集 DUT 的幅频和相频响应。每条曲线对应一次固定幅值的扫频结果,便于观察通带、衰减和相位变化。
-
-
-
-
-#### 二维扫频
-
-`sweep.frequency_response` 支持「请求 Vpp × 频率」二维扫频。每个网格点保留输入与输出波形、频率响应和质量状态,可进一步生成二维校准 LUT;安装 `.[report3d]` 后,还能在 HTML 报告中查看交互式三维增益曲面。
-
-
+
+
+ Instrument Control ·
+ Automated Measurement ·
+ Data Analysis ·
+ Open Source
+
+
-#### 多仪器 `run plan`
+
+
+ An open-source toolkit for automated laboratory measurement and instrument control.
+
+
-显式 `run plan` 可以把信号源、示波器、电源和万用表编排到同一条实验流程中。执行前先用 `run check` 做离线校验,再用 `run verify` 做连接和安全预检;执行过程中保留每个步骤的状态、测量结果、失败证据和恢复记录。
+---
-run 内的 Source / Power 基础写入会在实际 setter 前回读并比较状态;状态漂移会停止写入并写入差异。缺少完整 `scope.snapshot` 的驱动可通过 `scope status` 返回 `partial summary`,操作能力可用 `capability explain` 离线核对。需要固定 plan、配置和任意波形输入时,先生成 `run intent`,再用 `run plan --intent` 在打开仪器前核验摘要。
+
+ 中文文档
+ ·
+ English Overview
+ ·
+ 更新日志
+ ·
+ 仪器插件仓库
+
-典型流程是「信号源 → DUT → 示波器 / 万用表」。
+---
-```mermaid
-flowchart LR
- source[信号源] --> dut[DUT]
- dut --> scope[示波器]
- dut --> dmm[万用表]
- plan[run plan] --> source
- plan --> scope
- plan --> dmm
- scope --> artifacts[采集包 / 离线报告]
- dmm --> artifacts
-```
+> [!WARNING]
+> WaveBench 可以连接并控制真实实验设备。执行会改变仪器状态的命令前,应确认接线、输入阻抗、输出状态以及电压/电流限制。
-#### 示波器联合视图
+WaveBench 是面向电子设计调试和日常实验的 Python 测量台。它将仪器操作、显式 run plan 和可复查的实验产物放在同一条工作流中:先离线检查,再连接实验台,最后执行受控实验。
-`scope focus` 用一个事务配置一个或多个目标模拟通道,并可同时设置完整横向时间范围、各目标通道的
-V/div,以及是否隐藏插件 profile 声明的其他模拟通道:
+
-```bash
-wavebench scope focus \
- --channel 1 \
- --channel 2 \
- --time-range 0.01 \
- --vertical-scale 1=0.2 \
- --vertical-scale 2=0.5 \
- --hide-others
-```
+## 适用范围
-Core 不定义仪器型号、通道数量或数值范围;这些 guard 由当前插件的 descriptor profile 声明。
-操作会先读取 profile 全部模拟通道及受保护的时基、位置和偏置字段。成功后保留目标视图;任一写入
-或回读失败时恢复完整 baseline 并重新查询,恢复不完整则停止该 session 的后续写入。该命令不启动
-采集、不调用 autoscale,也不修改耦合或输入终端。
+- 用信号源、示波器、电源和万用表组成可复现的实验流程。
+- 用 `run check` 在不连接仪器的情况下检查 plan。
+- 将采集包、运行记录和离线 HTML 报告保存在同一实验产物中。
+- 用显式命令控制输出、采集和恢复,不在后台隐式执行 reset 或输出切换。
+- 通过已安装的 instrument plugin 扩展具体仪器型号。
-## 先在没有仪器时跑通
+## 无硬件快速开始
-下面的命令只生成和检查 plan,不会连接仪器,也不会打开输出。
-原生 Windows 与 Linux / WSL 均可运行离线命令。Windows 原生硬件访问使用
-`portalocker[win32]` 提供的跨进程锁;Windows 与 WSL 不共享同一锁域,同一台仪器应固定由一种运行环境访问。
+以下命令只安装包、列出模板并打印一个示例 plan;不会连接仪器或打开输出。
```bash
+git clone https://github.com/Scaxlibur/WaveBench.git
+cd WaveBench
python3 -m venv .venv
.venv/bin/python -m pip install -e .
-source .venv/bin/activate
-
-wavebench run template --list
-wavebench run template source-scope-sine --output /tmp/wavebench-demo.toml --force
-wavebench run check --plan /tmp/wavebench-demo.toml
-```
-
-PowerShell 示例:
-
-```powershell
-py -3.11 -m venv .venv
-.\.venv\Scripts\python.exe -m pip install -e ".[dev,analysis,tui]"
-.\.venv\Scripts\python.exe -m wavebench run template --list
-```
-
-Windows 串口资源使用 `COM3`、`COM10` 等形式;`\\.\COM10` 也会规范化为同一串口身份。
-VISA、SocketIO 和 USB 设备仍需安装对应的厂商驱动或后端。纯 Python 插件支持原生 Windows 的安装、升级、删除和恢复;包含原生 DLL 的插件不在本轮支持范围内。
-
-Windows 原生环境暂不保证与 WSL 进程对同一资源互相阻塞。需要使用 WSL 时,可继续使用
-[`scripts/wsl-run.ps1`](scripts/wsl-run.ps1) 作为兼容入口。
-
-查看终端界面时,可另外安装 TUI 依赖。`--fake` 使用模拟设备,不连接实验台:
-
-```bash
-.venv/bin/python -m pip install -e ".[tui]"
-wavebench tui --fake
+.venv/bin/python -m wavebench run template --list
+.venv/bin/python -m wavebench run template source-scope-sine --print
+.venv/bin/python -m wavebench run check --plan plans/closure_sine_1k.toml
```
-执行 plan 前,应先核对 plan 中的 source、power、scope 步骤和恢复范围。`run verify` 用于连接与安全预检,`run plan` 才会进行真实实验。
-
-## 内建支持
-
-| 入口 | 内建设备 | 主要用途 | 状态 |
-| --- | --- | --- | --- |
-| 示波器 | R&S RTM2000/RTM2032、RIGOL DS1104Z/DS1000Z | 波形读取、单次采集、多通道和截图 | 主包能力 |
-| 信号源 | RIGOL DG4000/DG4202 | 基本波形、频率控制、扫频和任意波上传 | 主包能力 |
-| 电源 | RIGOL DP800 | 状态、保护、设定值和输出控制 | 主包能力 |
-| 万用表 | RIGOL DM3000/DM3058 | 常用读数、功能和部分量程/触发状态 | 主包能力 |
-| run plan | source、rf_source、power、scope、dmm、sleep、频响步骤 | 多仪器编排、质量检查和恢复 | 主入口 |
-| TUI | 电源、万用表、信号源面板 | 人工查看和少量控制 | 实验性 |
-| 插件 | `wavebench.instruments` 外部 driver | 添加或替换特定仪器实现 | 可选 |
-
-详细的能力边界和参数见 [文档总览](docs/README.md)、[项目文档分类](docs/project/README.md) 及 `docs/project/reference/` 下的参考页。
-
-## RF 信号源
+最后一条命令以 `safety_limits=ok / 安全上限=通过` 结束时,表示示例 plan 已通过离线检查。完整的预期结果、Windows 命令和下一步见[无硬件快速开始](docs/getting-started/quickstart.md)。
-`rf_source` 是独立于普通 `source` 的仪器领域。DSG830 当前已开放只读状态、RF OFF 时的单字段 CW 配置、RF-OFF 内部正弦 AM/FM/PM 配置及按模式关闭、具有完整 safety 配置的 `rf_out` ON/OFF、RF-OFF internal/single Pulse 配置,以及 RF-OFF 的 frequency-only Step Sweep 配置。PM 的 production profile 限于 `1.25 rad`。A4-MO 已将受限 `rf_source.modulated_output_enable` 提升到 production:仅接受已激活且精确匹配的 AM `50 %`/`1 kHz`、FM `20 kHz`/`1 kHz`、PM `1.25 rad`/`1 kHz` profile,最大功率均为 `-50 dBm`;普通 `rf_source.output` 仍要求调制关闭。FM/PM 的 WaveBench CH2 分析只记录波形质量,不计量频偏或相偏。Step Sweep 固定为 `STEP`/`FWD`/`RAMP`/`LIN`,配置后保持 Sweep disabled。
-
-已完成的 A5 只覆盖一条后面板物理路径:DSG830 的「PULSE IN/OUT」按 output 方向、固定 internal/single/normal/`1 ms`/`100 μs` profile,提供 `rf_source.pulse_output` 与 `wavebench rf-source pulse-output`。它不启用 RF 输出,也不定义 Pulse input、`TRIGGER IN`、Sweep fire、sync/reference、Level Sweep 或 list。A5-0 仍仅是逻辑 Pulse/Sweep trigger configuration 的零写读取合同,DSG830 production descriptor 不声明 `rf_source.trigger_snapshot`。
-
-- 日常配置与操作:[RF 信号源使用指南](docs/project/guides/WaveBench_RF信号源使用指南.md)
-- 模型、安全语义和 capability 边界:[RF 信号源领域设计](docs/project/design/WaveBench_RF信号源设计.md)
-- Core/插件的同步计划和证据状态:[RF 信号源开发里程碑](docs/project/design/WaveBench_RF信号源开发里程碑.md)
-
-## 三条常用路径
-
-### source → scope 完整流程
-
-先用模板生成 plan,再检查它:
-
-```bash
-wavebench run template source-scope-sine --output plans/my-sine.toml
-wavebench run check --plan plans/my-sine.toml
-```
+## 支持范围
-确认接线、scope coupling 和安全限值后,才执行:
-
-```bash
-cp -n wavebench.example.toml wavebench.toml
-# 编辑 wavebench.toml,填写当前实验台的 resource;已有配置不要覆盖
-wavebench run verify --config wavebench.toml --plan plans/my-sine.toml
-wavebench run plan --config wavebench.toml --plan plans/my-sine.toml
-wavebench run report data/runs/
-```
-
-### power → DMM / scope
-
-DP800 的设定值、保护和输出是三类独立操作。示例计划见 [plans/README.md](plans/README.md)。厂商命令和型号资料由[仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins)维护;本仓库只说明 WaveBench 的调用边界。
-
-### 双通道频率响应
-
-使用 `source-scope-frequency-response` 模板可以生成 reference / response 双通道扫频 plan。基础频响采集不要求额外依赖;PCHIP、平滑样条和二维校准需要 `analysis`,PDF 报告需要 `pdf`,交互式三维 HTML 需要 `report3d`。详细说明见 [run plan 使用指南](docs/project/guides/WaveBench_run_plan_使用指南.md);执行前仍需确认真实接线。
-
-频响结果可用 `run compare` 离线比较多个 run,并用 `run resume` 生成缺失点补测清单;两条命令都不会连接仪器。每个已生成采集包的频响点会保存 `case_id`、`acquisition_id`、请求 Vpp 与参考通道实测 Vpp,便于复查测量来源。
-
-## 命令的安全边界
-
-| 类别 | 例子 | 说明 |
+| 类别 | Core 提供的通用能力 | 精确型号状态 |
| --- | --- | --- |
-| 离线 | `run schema`、`run template`、`run check`、`run report`、`run compare`、`run resume`、`capture inspect`、`tui --fake` | 不连接仪器;TUI 可能写本地日志 |
-| 连接读取 | `doctor`、`idn`、`status`、`run verify` | 会查询设备;仍应把它当作有状态的 I/O |
-| 修改设备 | `scope fetch/capture/autoscale`、source/power setter、output、`run plan`、非 fake TUI | 可能改变设置、触发采集或切换输出 |
-
-WaveBench 的默认行为包括:
-
-- 不自动发送 `*RST`;
-- 不因设定电压或幅度而自动打开输出;
-- 不自动改变示波器输入阻抗;可能的 50 Ω 输入需要显式确认;
-- `power set` 不改变输出开关,`power output` 不改变电压/限流设定;
-- 各仪器配置支持 `access = "read_write"`、`"read_only"` 或 `"disabled"`;`read_only` 只允许状态和配置读取,`disabled` 只保留离线命令;
-- 启用 source restore 后只覆盖文档注明的 basic 状态,不能当成完整通道快照;
-- run step 默认在失败后停止后续步骤;只有显式 `on_failure = "continue"` 才会继续。需要保护输出时,可用 `[safety] safety_gate = true` 和授权的 OFF 通道列表;安全门会先关闭目标输出再停止 run;
-- HTTP MCP 的工具入口需要认证,当前只提供只读工具;`/health` 是例外,不需要 token。它不提供 raw SCPI 或输出开关。
+| 示波器 | 读取、采集、截图和受 capability 约束的控制 | 由内建或已安装插件 descriptor 声明 |
+| 信号源/RF 信号源 | 显式配置、输出控制、run plan 和安全预检 | 由对应插件的 profile 与 evidence 声明 |
+| 电源/万用表 | 状态、设定、读数与 run plan 集成 | 由对应插件 descriptor 声明 |
+| 插件 | 发现、安装和公开插件 API | 具体型号、SCPI、quirk 和限制见[仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins) |
-外部 Python 插件按当前用户权限运行,不是安全沙箱。仅安装来源已确认的本地目录或 wheel;公开文档不得包含真实 IP、序列号、串口路径、凭据或实验产物。
+用[文档首页](docs/index.md)按任务进入正确页面;精确命令、字段和 capability 以程序输出、schema 与 descriptor 为准,而不是以本页摘要为准。
-## 按任务找文档
+## 安全摘要
-- 第一次安装和配置:[文档总览](docs/README.md)、[配置文件格式](docs/project/reference/WaveBench_配置文件格式.md)
-- 编写和检查 run plan:[run plan 使用指南](docs/project/guides/WaveBench_run_plan_使用指南.md)
-- 了解采集包和报告:[数据输出格式](docs/project/reference/WaveBench_数据输出格式.md)
-- 安装或开发插件:[插件用户指南](docs/project/guides/WaveBench_可安装仪器插件.md)、[插件开发指南](docs/project/contributing/WaveBench_插件开发指南.md)
-- 使用 TUI 或 HTTP MCP:[TUI 文档](docs/project/guides/WaveBench_TUI终端控制面板.md)、[HTTP MCP 文档](docs/project/guides/WaveBench_HTTP_MCP_只读接口.md)
-- 查厂商命令和历史验证:见[仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins);本仓库只保留 WaveBench 自身的接口和设计文档
+- `run schema`、`run template`、`run check` 和 `run report` 不连接仪器。
+- `doctor` 与 `run verify` 会查询真实设备;先确认资源与接线。
+- `run plan`、输出控制、采集和部分 TUI 操作可能改变仪器状态。
+- WaveBench 不自动执行 `*RST`,也不会因设置电压、幅度或频率而自动开启输出。
-目前中文文档覆盖最完整,英文入口提供安装、离线体验和安全摘要。命令、字段名和 schema 以程序输出为准。
+开始真实实验前,请阅读[执行一次实验](docs/how-to/run-an-experiment.md)和[配置实验台](docs/getting-started/configure-bench.md)。
-## 开发
-
-要求 Python 3.11 或更高版本。开发依赖和 optional extras 见 [pyproject.toml](pyproject.toml)。常用检查:
-
-```bash
-.venv/bin/python -m pip install -e ".[dev]"
-.venv/bin/ruff check .
-.venv/bin/python -m pytest -q
-```
+## 文档与贡献
-## 许可证与致谢
+- [文档首页](docs/index.md)
+- [示例计划及硬件边界](plans/README.md)
+- [管理仪器插件](docs/how-to/manage-plugins.md)
+- [插件开发](docs/development/plugin-development.md)
+- [更新日志](CHANGELOG.md)
WaveBench 使用 MIT 许可证。感谢 Linux DO 社区提供交流和支持。
diff --git a/docs/README.md b/docs/README.md
index 88a93a34..0494f5a9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,91 +1,8 @@
-# WaveBench 文档
+# WaveBench 文档旧入口
-[English](README_EN.md) · 中文
+当前中文文档入口为[文档首页](index.md)。此路径仅用于兼容旧链接。
-WaveBench 是一个用 Python 编写的实验室自动测量台,提供 CLI、实验性 TUI、显式 run plan、采集包和离线报告。当前开发线为 `0.8.26`,最新稳定 tag 为 `v0.8.0`。版本变化见 [更新日志](../CHANGELOG.md);旧版本原始文档可切换到对应 Git tag 查看。
-
-> [!WARNING]
-> 部分命令会连接并控制真实仪器。示例会区分离线检查、连接读取和硬件写入;执行写入前,应确认接线和限制值。
-
-## 从这里开始
-
-### 尚未连接仪器
-
-先阅读根目录的 [README](../README.md),完成安装和离线检查:
-
-```bash
-wavebench run template --list
-wavebench run template source-scope-sine --output /tmp/wavebench-demo.toml --force
-wavebench run check --plan /tmp/wavebench-demo.toml
-```
-
-安装 `.[tui]` 后可运行 `wavebench tui --fake` 检查 TUI 界面;该模式使用模拟设备。
-
-### 配置实验台
-
-- [配置文件格式](project/reference/WaveBench_配置文件格式.md):TOML 查找顺序、字段和安全限制。
-- 仪器型号命令和编程手册由 [仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins) 维护;本仓库只记录 WaveBench 的接入边界。
-
-### 配置示波器联合视图
-
-`wavebench scope focus` 接受重复的 `--channel`、可选的 `--time-range`、重复的
-`--vertical-scale CHANNEL=V_PER_DIV` 和 `--hide-others`。Core 只定义可移植事务:插件 profile
-声明模拟通道、数值范围、容差和 I/O 预算;Core 读取完整联合 baseline,成功后保留目标视图,失败时
-恢复并重新查询。未声明 `scope.focus_configure_v2` 的插件会在仪器 I/O 前拒绝操作。
-
-该命令会修改仪器状态,但不会启动采集、调用 autoscale、修改耦合或切换输入终端。执行前应核对
-接线、输入状态和插件 capability。
-
-### 使用 RF 信号源
-
-`rf_source` 不复用普通 `source` 的 Vpp、offset 或数字 channel 模型。先从 [RF 信号源使用指南](project/guides/WaveBench_RF信号源使用指南.md) 确认当前 production capability 和端接声明;DSG830 已开放固定 profile 的调制输出,以及唯一受验证的后面板 `pulse_in_out` output 路径。后者不代表 Pulse input、`TRIGGER IN` 或同步能力。需要实现新型号或查看证据门时,再阅读 [领域设计](project/design/WaveBench_RF信号源设计.md) 与 [开发里程碑](project/design/WaveBench_RF信号源开发里程碑.md)。
-
-### 执行实验
-
-- [run plan 使用指南](project/guides/WaveBench_run_plan_使用指南.md):模板、`run check`、`run verify`、执行、恢复和报告。
-- [数据输出格式](project/reference/WaveBench_数据输出格式.md):采集包、run、频响和校准产物。
-- [示例计划目录](../plans/README.md):每个计划的硬件副作用和适用范围。
-
-### 查询参数或接口
-
-- [CLI 形态](project/guides/WaveBench_CLI形态.md):命令入口和交互/非交互边界。
-- [配置文件格式](project/reference/WaveBench_配置文件格式.md)
-- [错误处理和日志策略](project/reference/WaveBench_错误处理和日志策略.md)
-- [HTTP MCP 只读接口](project/guides/WaveBench_HTTP_MCP_只读接口.md)
-- 插件接口以当前源码中的 Protocol、models 和对应插件文档为准。
-
-### 安装或开发插件
-
-- [可安装仪器插件用户指南](project/guides/WaveBench_可安装仪器插件.md):本地目录/wheel 的检查、安装、升级、卸载和恢复。
-- [插件开发指南](project/contributing/WaveBench_插件开发指南.md):V2 插件的接入流程和发布检查。
-- [可执行仪器插件 API 约定](project/reference/plugins/WaveBench_可执行仪器插件API.md):descriptor、factory、context、capability 和兼容边界。
-- [新增仪器驱动指南](project/contributing/WaveBench_新增仪器驱动指南.md):从 driver 到 CLI、run plan 和文档的接入路径。
-- [声明式 SCPI 插件](project/reference/plugins/WaveBench_声明式SCPI插件.md) · [插件注册表](project/reference/plugins/WaveBench_插件注册表.md) · [插件市场索引](project/reference/plugins/WaveBench_插件市场索引.md)
-
-### 理解设计取舍
-
-- [项目边界](project/design/WaveBench_项目边界.md)
-- [设备抽象层](project/design/WaveBench_设备抽象层.md)
-- [多仪器流程设计](project/design/WaveBench_多仪器协同流程设计.md)
-- [sweep 状态保存与恢复](project/design/WaveBench_sweep状态恢复设计.md)
-- [TUI 终端控制面板](project/guides/WaveBench_TUI终端控制面板.md)
-
-目录分类见 [project/README](project/README.md)。本页只负责入口,不把阶段记录当作当前使用说明。
-
-## 命令的副作用
-
-| 类别 | 例子 | 是否连接仪器 |
-| --- | --- | --- |
-| 离线 | `run schema`、`run template`、`run check`、`run report`、`capture inspect`、`tui --fake` | 不连接仪器;TUI 可能写本地日志 |
-| 连接读取 | `doctor`、`idn`、`status`、`run verify` | 是,仅读取或做预检 |
-| 修改状态 | `scope focus/fetch/capture/autoscale`、source/power setter、output、`run plan` | 是,可能写入、触发或改变输出 |
-
-`run check` 不代表 plan 可以安全执行。它只检查 TOML 和字段;真正执行前,还要核对接线、scope coupling、输出状态、保护限值和 restore 条款。
-
-## 历史资料与厂商手册
-
-版本历史统一放在根目录的 [CHANGELOG.md](../CHANGELOG.md)。厂商手册和 SCPI 摘录由 [仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins) 维护;本仓库只保留 WaveBench 自身的接入边界、接口、配置和安全语义。
-
-## 语言和事实源
-
-中文页面覆盖最完整,英文入口提供对应的安装、离线体验和安全摘要。版本以 `pyproject.toml` 和 Git tag 为准;CLI 语法以 `wavebench --help`,run plan 语法以 `wavebench run schema` 和 `run template --list` 为准。公开文档中的示例资源必须使用保留地址或占位符,不得写入实验室真实 IP、序列号或串口路径。
+- 首次离线体验:[无硬件快速开始](getting-started/quickstart.md)
+- 按任务查阅:[文档首页](index.md)
+- 版本变更:[更新日志](../CHANGELOG.md)
+- 具体型号、SCPI 和厂商限制:[仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins)
diff --git a/docs/README_EN.md b/docs/README_EN.md
index 40ceb08a..75aafd49 100644
--- a/docs/README_EN.md
+++ b/docs/README_EN.md
@@ -1,91 +1,27 @@
# WaveBench Documentation
-[中文文档](README.md) · English
+[中文完整文档](index.md) · [Project README](../README.md) · [Changelog](../CHANGELOG.md)
-WaveBench is a Python measurement bench for laboratory debugging. It combines explicit instrument commands, run plans, capture packages, and offline reports. It requires Python 3.11 or newer. The current development line is `0.8.26`; the latest stable tag is `v0.8.0`.
-
-> [!WARNING]
-> Some commands connect to and change real instruments. Check wiring, input impedance, output state, and voltage/current limits before running a hardware action.
+The current complete documentation is maintained in Chinese. This page is an English orientation page; command syntax, schemas, and model-specific support are defined by the installed version of WaveBench and its instrument plugins.
## Start without instruments
-The following commands generate and check a plan locally. They do not connect to instruments or enable an output:
-Native Windows and Linux/WSL are supported for offline commands. Native Windows uses the
-`portalocker[win32]` lock backend; native Windows and WSL do not share a lock domain.
-
```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e .
-source .venv/bin/activate
-
-wavebench run template --list
-wavebench run template source-scope-sine --output /tmp/wavebench-demo.toml --force
-wavebench run check --plan /tmp/wavebench-demo.toml
-```
-
-PowerShell equivalent:
-
-```powershell
-py -3.11 -m venv .venv
-.\.venv\Scripts\python.exe -m pip install -e ".[dev,analysis,tui]"
-.\.venv\Scripts\python.exe -m wavebench run template --list
+.venv/bin/python -m wavebench run template --list
+.venv/bin/python -m wavebench run template source-scope-sine --print
+.venv/bin/python -m wavebench run check --plan plans/closure_sine_1k.toml
```
-Use `COM3` or `COM10` for native Windows serial resources. `\\.\COM10` is normalized to the
-same serial identity. VISA, SocketIO, and USB access still depend on the corresponding vendor
-driver or backend. Pure-Python plugins support the native Windows lifecycle; native-DLL plugins
-require separate validation. The existing [`scripts/wsl-run.ps1`](../scripts/wsl-run.ps1) bridge
-remains available for WSL workflows.
-
-For the terminal UI, install `.[tui]` and run `wavebench tui --fake`. The fake mode uses simulated devices.
-
-## Built-in support
-
-| Entry point | Built-in families | Scope |
-| --- | --- | --- |
-| Oscilloscope | R&S RTM2000/RTM2032, RIGOL DS1104Z/DS1000Z | Waveform reads, captures, multiple channels, screenshots |
-| Signal generator | RIGOL DG4000/DG4202 | Basic waveforms, frequency control, sweeps, arbitrary-wave uploads |
-| Power supply | RIGOL DP800 | Status, protection, setpoints, and explicit output control |
-| DMM | RIGOL DM3000/DM3058 | Common readings and selected function/range/trigger state |
-| Run plans | source, power, scope, dmm, sleep, frequency-response steps | Multi-instrument execution and offline checks |
-| TUI | Power, DMM, and source panels | Experimental manual control |
-| Plugins | `wavebench.instruments` drivers | Optional, explicitly selected extensions |
-
-## Configure a joint oscilloscope view
-
-`wavebench scope focus` accepts repeated `--channel` arguments, optional `--time-range`, repeated
-`--vertical-scale CHANNEL=V_PER_DIV`, and `--hide-others`. Core defines only the portable
-transaction. Each plugin descriptor profile owns its analog-channel set, numeric request guards,
-tolerances, and I/O budgets. Core reads the complete joint baseline, preserves the requested view
-after success, and restores then freshly verifies that baseline after failure. A plugin that does
-not declare `scope.focus_configure_v2` is rejected before instrument I/O.
-
-This command changes front-panel state. It does not start acquisition, invoke autoscale, change
-coupling, or switch input termination. Wiring, input state, and the selected plugin capability must
-be checked before execution.
-
-## Find a guide
-
-- Setup and configuration: [configuration format](project/reference/WaveBench_配置文件格式.md)
-- Run plans and reports: [run plan guide](project/guides/WaveBench_run_plan_使用指南.md)
-- Capture and run artifacts: [data output format](project/reference/WaveBench_数据输出格式.md)
-- Install or develop plugins: [plugin user guide](project/guides/WaveBench_可安装仪器插件.md) and [plugin development guide](project/contributing/WaveBench_插件开发指南.md)
-- TUI and read-only HTTP MCP: [TUI](project/guides/WaveBench_TUI终端控制面板.md) and [HTTP MCP](project/guides/WaveBench_HTTP_MCP_只读接口.md)
-- Example plans and their hardware boundaries: [plans README](../plans/README.md)
-- RF-source domain and milestones: [guide](project/guides/WaveBench_RF信号源使用指南.md), [design](project/design/WaveBench_RF信号源设计.md), and [milestones](project/design/WaveBench_RF信号源开发里程碑.md). Core provides M0–M4 and one bounded A5 Pulse Output contract; DSG830 A1/A2/A3/A4/A4-MO/A5 evidence permits production identity, snapshot, OFF-only `rf_source.cw_configure`, RF-OFF internal-sine `rf_source.modulation_configure`, `rf_source.modulation_disable`, safety-gated `rf_source.output` ON/OFF, fixed-profile modulated output, internal/single Pulse configuration, `rf_source.pulse_output`, and frequency-only Step Sweep configuration that remains disabled. A5 covers only the declared `pulse_in_out` output route, not Pulse input, `TRIGGER IN`, trigger, sync, or Sweep execution. PM is limited to the verified `1.25 rad` production profile.
-
-Most detailed pages are currently maintained in Chinese. Commands, identifiers, and schemas should match across languages.
-
-## Check before running hardware
+The last command succeeds with `safety_limits=ok / 安全上限=通过`. It performs no instrument I/O. See the [Chinese Quickstart](getting-started/quickstart.md) for expected output, Windows commands, and the next steps.
-| Class | Examples | Instrument I/O |
-| --- | --- | --- |
-| Offline | `run schema`, `run template`, `run check`, `run report`, `capture inspect`, `tui --fake` | No instrument I/O; TUI may write a local log |
-| Connected read/preflight | `doctor`, `idn`, `status`, `run verify` | Yes, for queries and checks |
-| State-changing | `scope focus/fetch/capture/autoscale`, source/power setters, output commands, `run plan` | Yes; may change setup, trigger acquisition, or switch output |
+## Safety boundary
-WaveBench does not implicitly reset instruments, enable outputs, or change oscilloscope input impedance. `power set` and `power output` are separate operations. When enabled, source restoration covers only the documented basic fields; it is not a full channel snapshot.
+- `run schema`, `run template`, `run check`, and `run report` are offline.
+- `doctor` and `run verify` query configured instruments.
+- `run plan`, output commands, capture commands, and non-fake TUI controls may change instrument state.
-Executable Python plugins run with the current user's permissions. Install only a trusted local source directory or wheel, and keep real resources, serial numbers, credentials, and generated artifacts out of public documentation.
+Before a hardware action, check wiring, input impedance, output state, and voltage/current limits. Model-specific SCPI, limits, and evidence belong to the [instrument plugin repository](https://github.com/Scaxlibur/wavebench-instrument-plugins).
-The root [README](../README.md) is the short project entry point. The [Chinese documentation index](README.md) and [project document map](project/README.md) group the longer pages by task. Version history lives in the root [CHANGELOG](../CHANGELOG.md); vendor manuals are maintained in the [instrument plugin repository](https://github.com/Scaxlibur/wavebench-instrument-plugins).
+Use the [Chinese documentation index](index.md) to find guides, Reference pages, Concepts, development material, RFCs, and release history.
diff --git a/docs/archive/artifacts-reference-pre-migration.md b/docs/archive/artifacts-reference-pre-migration.md
new file mode 100644
index 00000000..3bc6e87f
--- /dev/null
+++ b/docs/archive/artifacts-reference-pre-migration.md
@@ -0,0 +1,874 @@
+# 归档:重构前的 WaveBench 数据输出格式
+
+> [!NOTE]
+> 本页保留文档重构前的采集、run、频响和报告材料。当前运行产物字段以 writer、typed result 和 loader 为准;请阅读[运行产物 Reference](../reference/artifacts.md)。
+
+## 设计目标
+
+数据输出围绕三个要求设计:
+
+1. 结果便于阅读;
+2. Python 可以直接分析;
+3. 采集过程和原始证据可以追溯。
+
+当前阶段只支持:
+
+```text
+CSV + NPY + JSON + commands.log
+```
+
+暂不考虑 MATLAB 兼容、Parquet、数据库和自动报告。
+
+## 当前实现状态(2026-04-29)
+
+`9c9cc32 feat: fetch and capture RTM2032 waveforms` 已实现单通道采集包输出:
+
+```text
+data/raw/20260429_162450_square_1khz/
+├─ ch1.csv
+├─ ch1.npy
+├─ metadata.json
+└─ commands.log
+```
+
+实机样例来自 RTM2032 自带约 1 kHz 方波:
+
+```text
+samples = 10000
+time = -1.000000e-03 .. 9.998000e-04 s
+dt = 2.000000e-07 s
+voltage = about -0.5 .. 0.62 V
+```
+
+当前 `capture` 已推进为 `SINGle + *OPC?` 单次采集后再读取波形并打包。
+
+
+## 采集包
+
+每次采集生成一个独立目录,称为「采集包」。
+
+单通道示例:
+
+```text
+data/raw/20260429_011530_ch1/
+├─ ch1.csv
+├─ ch1.npy
+├─ metadata.json
+└─ commands.log
+```
+
+以后如果支持多通道:
+
+```text
+data/raw/20260429_011530_ch1_ch2/
+├─ ch1.csv
+├─ ch1.npy
+├─ ch2.csv
+├─ ch2.npy
+├─ metadata.json
+└─ commands.log
+```
+
+如果以后支持截图,可增加:
+
+```text
+screenshot.png
+```
+
+## 目录命名规则
+
+默认规则:
+
+```text
+YYYYMMDD_HHMMSS_