diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..0fa32f7e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md + +WaveBench 是 MIT 许可的 Python 3.11+ 仪器测量台(VISA/SCPI、run plan、报告、TUI、插件 API)。本仓库的 `master` 是当前主线:上游基座 + 比赛作品包 + advisor 实施。 + +## 分支定位 + +- `master`(当前分支)是主线:`44506c4`(上游)→ `68148a0`/`a9e49e3`(比赛作品包)→ `6dc5263`/`51d6136`/`514b76f`(advisor:RFC、草案、同意门 + decision artifact)。它同时被上游 PR #27 提出——**往 `master` 提交前先想清楚是否要让上游看到这些内容**。 +- 远端:`origin` = `Epslion404/wavebench`(fork,上游 PR 的来源);`tyai` = `Epslion404/TYAI-for-wavebench`(**private 比赛仓库**,默认分支 `master`,issue 与 PR 都在那里跟踪)。 +- `competition` 是另一条内容线(孤儿根提交):含同一批 advisor 改动与文档,但**比赛作品包与 `master` 已经分叉**(见「已知不一致」);它不向上游提 PR,只用于比赛提交。 +- 其他分支:`docs/advisor-plugin-rfc`(上游 PR #20)、`split/agent-scope-observe-advise`(上游 PR #4)。 +- CI 只在 push 到 `master`/`main` 或 PR 时触发;`competition` 不跑,本地检查要自己执行(`tyai` 上虽然会触发,但目前空转失败)。 + +## 目录职责 + +- `src/wavebench/`:核心实现(cli、services、drivers、transport、plugins、tui)。行为事实源依次为实现、`--help`、`run schema`、`run template --list`、`wavebench.example.toml`;文档和技能正文不能覆盖它们。 +- `比赛区/天翼AI电子科研Skill/`:比赛作品包(TeleAgent Skill)。`src/tianyi_electronics_skill/` 只用标准库(socket SCPI + 信号分析),`numpy`/`pyvisa` 是可选 extras。 +- `比赛区/天翼AI电子科研Skill/wavebench/`:根项目的**独立快照**。与根目录同名的文件内容逐字节相同,另有 `skills/` 与根 `.agents/skills/` 重复;根目录缺少 `tool-of-rei/release-notes/`。改根目录不会影响它,反之亦然,要同步就整目录覆盖。 +- `测试区/.temp/full_discover.py`:临时脚本(pyvisa 枚举 + 硬编码网段 `10.17.216.0/24` 的 SCPI 端口扫描),不是测试套件。 +- `docs/project/`:项目带文档(不进入文档站)。advisor 线见 `docs/project/rfcs/WaveBench_advisor插件RFC.md` 与 `docs/project/design/advisor-draft/`(seam 草案、阶段 1 骨架、规划文件);自定义设备与协议支持的规范/TODO 只在 `competition` 分支上。 +- `.agents/skills/`:两个 Agent Skill 入口 —— `wavebench/SKILL.md`(真机操作、安全门、开发验证)和 `wavebench-docs/SKILL.md`(文档治理)。根 `SKILL.md` 只是指向前者的指针;排查真机行为时按需读 `wavebench/references/`,不要凭记忆推断。 + +## 命令(仓库根) + +```bash +python -m venv .venv # 仓库不提交 .venv;要求 >=3.11(CI 只验证 3.11/3.12) +.venv/bin/python -m pip install -e ".[dev,analysis,tui]" # Windows: .venv\Scripts\python.exe +.venv/bin/python -m pytest -q # 离线全量(pythonpath=src,testpaths=tests) +.venv/bin/python -m pytest -q tests/test_run_plan.py # 聚焦单个测试文件 +.venv/bin/python -m ruff check . # line-length=100,select E4/E7/E9/F +python scripts/generate_docs.py --check # CI 阻断:受管理 Reference 必须与源码同步 +python scripts/generate_docs.py # 重新生成上述 Reference +python .agents/skills/wavebench-docs/scripts/audit_docs.py --quiet-warnings +python scripts/docs_impact.py --base --head HEAD # 列出本次代码改动影响的候选文档页 +mkdocs build --strict # 文档站,需 pip install -e ".[docs]" +``` + +- `pdf` extra(weasyprint)只在 Linux CI 安装,Windows 用 `dev,analysis,tui` 即可。 +- 不连接仪器的命令:`run schema`、`run template --list`、`run check --plan `、`run report`。 +- 作品包在其自身目录内独立验证:`python -m pytest -q`;离线演示 `python -m tianyi_electronics_skill analyze --demo`。 +- GitHub 操作手动带代理:`git -c http.proxy=http://127.0.0.1:7897 `;`gh` 需要 `$env:HTTPS_PROXY`(本机直连会超时)。 +- 仓库外的本地助手(取 PR/commit、merge、cherry-pick,自动带代理并拦「孤儿历史 merge」):`D:\Documents\Code\TY_AI\tools\wbflow.ps1`,例如 `powershell -NoProfile -File pr-cherry 20 -Repo `。 + +## 真机与数据边界 + +- 默认离线:driver/service 测试用 fake transport,实机记录不能替代可重复的离线测试。 +- `run check` 不碰仪器;`doctor`、`run verify` 会查询真实设备;setter、输出切换、采集、扫频必须取得明确授权。 +- 不自动执行 `*RST`,不因设置电压/幅度/频率而自动打开输出;不静默放宽安全上限,不自动追加 `--allow-50ohm`。 +- 被 gitignore 覆盖:`wavebench.toml`、`/data/`、`tool-of-rei/`、`.codegraph/`、`.agents/*`(两个 skill 除外)。不要把真实仪器地址或序列号写入跟踪文件。 + +## 已知不一致 + +- `docs/development/documentation.md` 要求给文档审计器传 `--term-allowlist`,但 `audit_docs.py` 已无该参数,照抄会报未知参数。 +- **两条线的比赛包已经分叉**(`git diff --shortstat master competition -- 比赛区` → 约 32 文件/+1954/−780):`master` 版含 `artifacts/` 采集产物、`examples/protocols/vendor_can_waveform.json`、`src/.../artifacts.py`;`competition` 版含 P0 拆分(`scpi.py`、`dialects.py`、`analysis.py`、`waveform.py`、`fetch` 子命令、错误队列)与 `plot.py`、`protocols.py`,且没有 zip 与 `artifacts/`。合并或取舍前先确认哪条线是要提交的实现。 +- `比赛区/天翼AI电子科研Skill/src/tianyi_electronics_skill/bridge.py` 有 28 个 `E701`/`E702`(一行多语句),会让 `ruff check .` 变红。 +- 比赛作品包不再把 zip 归档提交进仓库:以后的打包走 GitHub Release。 +- 提交信息沿用上游约定:`feat|fix|docs|test|chore|refactor|perf(scope): 说明`,正文以英文为主。 diff --git a/docs/project/README.md b/docs/project/README.md index 4517bbd9..f6bc1c5d 100644 --- a/docs/project/README.md +++ b/docs/project/README.md @@ -6,5 +6,6 @@ - 执行实验:[执行一次实验](../how-to/run-an-experiment.md) - 查询 run plan 字段:[run plan Reference](../reference/run-schema.md) - 查看 RFC 与历史:[现有 RFC 索引](rfcs/README.md)和[更新日志](../../CHANGELOG.md) +- advisor 插件类别:[RFC](../project/rfcs/WaveBench_advisor插件RFC.md)与[草案目录](design/advisor-draft/README.md) 具体型号、SCPI、profile、quirk 和实机 evidence 由[仪器插件仓库](https://github.com/Scaxlibur/wavebench-instrument-plugins)维护。 diff --git a/docs/project/design/advisor-draft/README.md b/docs/project/design/advisor-draft/README.md new file mode 100644 index 00000000..9788b245 --- /dev/null +++ b/docs/project/design/advisor-draft/README.md @@ -0,0 +1,242 @@ +# WaveBench × Jev 第 0 批:seam 设计草案(v2) + +> 状态:**设计草案**,已随支线 `feat/advisor-plugin-category` 入仓(本目录)。原始草案副本保留在 +> `D:\Documents\Code\jev-seam-draft`;RFC 正文见 `docs/project/rfcs/WaveBench_advisor插件RFC.md`(PR #20)。 +> +> 相关文件:[findings.md](findings.md)(一手事实核验)、[task_plan.md](task_plan.md)(阶段与决策记录)、 +> [progress.md](progress.md)(执行与验证记录)、[core_skeleton/](core_skeleton/README.md)(阶段 1 可跑骨架)。 +> v2 相对 v1 的变更:形态改为"插件"后**发现契约阻塞**(见 §1);新增数据边界的显式确认机制(§5); +> artifact 落点改为挂在对应 run 目录(§6)。 + +## 0. 结论摘要 + +- **形态**:按现有插件契约,advisor **不能**注册为 WaveBench 插件(§1 有源码证据)。要么改 Core 的 + 插件模型(路径 A),要么退回独立包(路径 B)。我建议**先 B 验证价值,再决定是否动 Core**。 +- **数据边界**:默认关闭 + 显式 opt-in + **发送前预览并要求确认**,同意记录落盘;非交互场景 + 一律 fail-closed(§5)。 +- **落点**:`data/runs//decisions/-.json`,**附加式**,不改 Core 拥有的任何文件(§6)。 + +## 1. 形态:为什么"插件"现在做不到 + +### 事实(本仓源码,已核对) + +| 位置 | 内容 | +| --- | --- | +| `src/wavebench/plugins/package_inspect.py:375-380` | 源包必须声明 `[wavebench.instruments]`,否则 `ConfigError("plugin must declare [wavebench.instruments] entry points / 插件必须声明可执行仪器 entry point")` | +| `src/wavebench/plugins/package_inspect.py:390-391` | wheel 的 `entry_points.txt` 缺该组同样报错 | +| `src/wavebench/plugins/registry.py:28` | `ENTRY_POINT_GROUP = "wavebench.instruments"` | +| `src/wavebench/plugins/api.py:6` | `PluginKind = Literal["scope","source","rf_source","power","dmm","sweep_analyzer"]` | +| `src/wavebench/plugins/registry.py:140-146` | 校验 `kind` 必须在上述集合内,且每个 capability 必须带 `"{kind}."` 前缀 | +| `src/wavebench/plugins/lifecycle.py:868-887` | 安装后按 `wavebench.instruments` 逐条加载 `descriptor_from_entry_point` 并 `_validate_descriptor` | + +**结论**:插件系统在设计上是"仪器插件",`kind`、capability 前缀、descriptor 校验都绑定仪器语义。 +一个只做判断、没有仪器 capability、没有 SCPI 的 advisor 无法通过任何一条安装路径。 + +### 路径 A:扩展 Core 插件模型("真插件") + +最小 Core delta: + +1. `plugins/api.py`:新增插件类别(例如独立 entry point group `wavebench.advisor` + 独立 dataclass), + 而不是塞进 `PluginKind`——advisor 与 `scope/source/...` 不是同一维度,混进 `Literal` 会污染 + capability 前缀规则。 +2. `plugins/registry.py`:注册/校验 advisor 插件(id、版本、所需 Core 版本门、声明它会外发哪些字段)。 +3. `plugins/package_inspect.py` + `lifecycle.py`:允许该组通过 `package check` / `install` / `installed` / + `remove`,并像 source/rf_source 那样加依赖校验。 +4. CLI:`plugin list/info/doctor` 展示 advisor 类别;文档新增 advisor 插件开发页(含"会外发数据"声明)。 +5. 测试:安装/卸载/漂移、无网络时的行为、拒绝系统 Python 等既有门禁全覆盖。 + +代价与风险: + +- 这是对 Core 公共契约的扩展,需要维护者同意;工作量和审查成本明显高于本 PR; +- 与 PR #4 抢同一个"MCP/agent 生态"叙事,维护者可能要求先统一设计; +- `plugin install` 是**离线、`--no-deps`** 的。`typesafe-sdk` 依赖 `httpx2/pydantic/pydantic-core/tenacity`, + 这些必须由操作者预装,或被 advisor 插件显式声明并由新规则校验; +- 插件能力目前默认不导入第三方 entry point(只显式加载),advisor 需要同样的显式开关。 + +### 路径 B:独立包(不是插件) + +作为普通 wheel/受控 venv 安装,通过既有 CLI 与 artifact 交互,**零 Core 改动**。 +代价:不能用 `wavebench plugin install` 管理,不走插件账本与漂移检测。 + +### 建议 + +**先走 B**:用一个不改变任何 Core 契约的集成包验证 §9 的 kill criteria。只有在"确实打败了确定性基线" +之后,再把 advisor 类别作为**独立的 Core 提案**(独立 PR、独立审查),而不是塞进本 PR。 + +## 2. 四个纯函数 + 一个门面 + +```text +build_state(...) -> DecisionState # 确定性;数字在代码里分桶 +build_questions(...) -> {qid: QuestionSpec} # 选项集来自代码拥有的 catalog +preview_request(...) -> PreviewPayload # 发送前给操作员看的全文 +apply_thresholds(...) -> [Recommendation] # 概率 -> 动作;阈值随结果记录 +record_decision(...) -> DecisionArtifact # wavebench.decision.v1,写进 run 目录 +Advisor.advise(...) -> DecisionArtifact # 唯一持有 client 的门面,且必须先过同意门 +``` + +契约:seam 内无网络;同输入同字节;模型不产出命令文本;阈值显式落盘;模型版本固定 +`jev-1.13.0` 并同时记录服务回报的 `reported_model`。 + +## 3. 不可信状态规则(不变) + +`DecisionState.fields` 只放代码已计算/已分桶的值;`DecisionState.untrusted` 放仪器回读、日志文本、 +操作员原话,逐条带 `source`。不变量:`instructions`/`criteria` **不得插值任何 untrusted 文本**(有单测)。 + +## 4. 选项集的 canonical 来源(不变) + +| 用途 | 来源 | +| --- | --- | +| 自然语言路由 | `python -m wavebench --help` 域列表 + `run template --list` + `capability explain` | +| 异常归因 | `docs/how-to/troubleshooting.md` 小节索引;型号相关原因归插件仓,需拆两份 catalog | +| 下一步检查 | 由 catalog 的 `next_check` 派生 | + +## 5. 数据边界:显式确认(按你的决定) + +三级门,逐级更严;**默认全关**: + +1. **默认关闭**:未配置 `[advisor] enabled = true` 或未显式传 `--advisor`,seam 不构造任何请求。 +2. **显式 opt-in**:命令行/配置必须明确开启,且配置里不含 API key 值(按本仓既有约定,key 只走环境变量)。 +3. **发送前预览 + 确认**:`--advisor-preview` 打印**即将外发的完整 payload**(含每个 untrusted span + 的原文与来源、字段清单、字节数、`payload_sha256`),不联网;确认由 `--accept-external-state` + 或交互式 y/N 给出。 + +非交互场景(`run plan`、CI、MCP 工具)**一律拒绝**:没有显式同意就返回 +`status="refused"`、`reason="external_state_consent_not_granted"`,且**不发起网络调用**。 + +同意记录必须落盘(放在 artifact 的 `consent` 块)。裁定 4c:默认按次确认,可登记"本 run 内有效", +且同意**绑定 endpoint 集合与允许字段集**: + +```json +"consent": { + "granted": true, + "valid_for": "run", + "run_id": "20260928_0740_loop_gain", + "granted_by": "operator", + "granted_at": "2026-09-28T07:40:10Z", + "endpoint_hosts": ["api.typesafe.ai"], + "allowed_state_fields": ["run_status", "cycles_bucket", "points_per_cycle_bucket"], + "payload_sha256": "9f2c...", + "payload_bytes": 812, + "accepted_data_leaves_machine": true +} +``` + +同意失效规则(任一命中即要求重新确认,且**不发起调用**): + +| 情形 | `reason` | +| --- | --- | +| 未同意 | `external_state_consent_not_granted` | +| endpoint 集合变化 | `consent_invalidated_by_endpoint_change` | +| 允许字段集变化 | `consent_invalidated_by_allowed_field_change` | +| run 级同意用于另一次 run | `consent_invalidated_by_run_change` | +| run 级同意但没有 run 目标 | `run_scoped_consent_requires_run_target` | + +`valid_for="run"` 时必须携带 `run_id`(构造期即校验)。这样"几个月前的配置"不会在插件声明 +悄悄变化后继续把新数据发出去。 + +其它硬规则: + +- **API key 永不进 artifact / 日志 / 错误信息**; +- 字段级白名单:`state.fields` 只允许注册过的键,出现新键即拒绝(避免顺手把新字段送出去); +- untrusted span 必须在预览里逐条可见,且允许单独剔除(`--exclude-untrusted `); +- 外发失败/未同意/被剔除,都不影响确定性路径(§7)。 + +## 6. 落点:挂在对应 run 目录(按你的决定) + +```text +data/runs/_