From 573cea6cd1cb34eb5689d6638caced1b7bd017d8 Mon Sep 17 00:00:00 2001 From: conanxu <1845830029@qq.com> Date: Mon, 10 Aug 2026 19:22:10 +0800 Subject: [PATCH] feat: add portable MATLAB runner skill Co-authored-by: njustar2002 --- .github/workflows/ci.yml | 2 + .gitignore | 4 + AGENTS.md | 4 +- CONTRIBUTORS.md | 15 ++ PUBLISHING.md | 9 + README.md | 7 + README.zh-CN.md | 7 + SECURITY.md | 10 + SKILL.md | 1 + THIRD_PARTY.md | 23 ++ skills/matlab-runner/CHANGELOG.md | 200 +++++++++++++++++ skills/matlab-runner/NORMALIZATION.md | 26 +++ skills/matlab-runner/PROVENANCE.yaml | 30 +++ skills/matlab-runner/README.md | 58 +++++ skills/matlab-runner/README.zh-CN.md | 54 +++++ skills/matlab-runner/SKILL.md | 203 ++++++++++++++++++ skills/matlab-runner/examples/README.md | 33 +++ skills/matlab-runner/examples/matrix_report.m | 28 +++ .../matlab-runner/examples/plot_damped_sine.m | 64 ++++++ .../matlab-runner/examples/test_vector_norm.m | 12 ++ .../matlab-runner/references/client-setup.md | 24 +++ .../references/file-session-safety.md | 86 ++++++++ .../references/numerical-safety.md | 126 +++++++++++ .../references/resource-safety.md | 125 +++++++++++ .../tests/scenarios/final-acceptance.md | 67 ++++++ .../tests/test_matlab_runner_package.py | 68 ++++++ skills/registry.yaml | 8 + tests/test_matlab_runner_integration.py | 145 +++++++++++++ tests/test_repository_shape.py | 56 ++++- 29 files changed, 1486 insertions(+), 9 deletions(-) create mode 100644 skills/matlab-runner/CHANGELOG.md create mode 100644 skills/matlab-runner/NORMALIZATION.md create mode 100644 skills/matlab-runner/PROVENANCE.yaml create mode 100644 skills/matlab-runner/README.md create mode 100644 skills/matlab-runner/README.zh-CN.md create mode 100644 skills/matlab-runner/SKILL.md create mode 100644 skills/matlab-runner/examples/README.md create mode 100644 skills/matlab-runner/examples/matrix_report.m create mode 100644 skills/matlab-runner/examples/plot_damped_sine.m create mode 100644 skills/matlab-runner/examples/test_vector_norm.m create mode 100644 skills/matlab-runner/references/client-setup.md create mode 100644 skills/matlab-runner/references/file-session-safety.md create mode 100644 skills/matlab-runner/references/numerical-safety.md create mode 100644 skills/matlab-runner/references/resource-safety.md create mode 100644 skills/matlab-runner/tests/scenarios/final-acceptance.md create mode 100644 skills/matlab-runner/tests/test_matlab_runner_package.py create mode 100644 tests/test_matlab_runner_integration.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc68336..ac9002c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,3 +35,5 @@ jobs: run: python -m unittest discover -s tests -v - name: Test math-glossary run: python -m unittest discover -s skills/math-glossary/tests -v + - name: Test matlab-runner adapter + run: python -m unittest discover -s skills/matlab-runner/tests -v diff --git a/.gitignore b/.gitignore index 1e9c386..b1bef07 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ glossary_*.xlsx glossary_backup_*.json 修改记录.md 扩充记录.md + +# Local MATLAB runner artifacts. +skills/matlab-runner/examples/*.asv +skills/matlab-runner/examples/damped_sine*.png diff --git a/AGENTS.md b/AGENTS.md index 292c854..43f989c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,9 @@ Run: ```bash python3 -m unittest discover -s tests -v python3 -m unittest discover -s skills/math-glossary/tests -v +python3 -m unittest discover -s skills/matlab-runner/tests -v ``` Package-level tests must use temporary directories and must not mutate source -fixtures or a user's glossary. +fixtures, a user's glossary, MATLAB files, or a shared MATLAB session. Static +`matlab-runner` tests do not establish that MATLAB executed successfully. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 57a3c37..bb4030f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,3 +10,18 @@ The private intake ledger retains the submitted Chinese name and original filename. Public repository metadata uses the standardized English form only. + +## matlab-runner + +- Original contributor identifier: `njustar2002` +- GitHub-associated email provided by the repository owner: + `njustar2002@vip.163.com` +- VeryMath platform-neutral normalization: preserves the contributor workflow + while replacing client-specific paths and fixed MCP tool prefixes +- Source-publication authorization: approved for MIT publication in this + repository + +The integration commit records the contributor with a `Co-authored-by` trailer. +GitHub can attribute the contribution when the supplied email is verified on +the contributor's account; this repository does not infer a public account +handle from the email address. diff --git a/PUBLISHING.md b/PUBLISHING.md index 2d518cc..f2241e6 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -17,6 +17,7 @@ python -m pip install \ -r skills/math-glossary/requirements-ai.txt python -m unittest discover -s tests -v python -m unittest discover -s skills/math-glossary/tests -v +python -m unittest discover -s skills/matlab-runner/tests -v git status --short ``` @@ -76,3 +77,11 @@ After the remote URL and passing CI run exist: 提交作为 `main` 推送,CI 通过后再创建 `v0.1.0` Release。最后才同步 AI4Math-Skill-Library 与 VeryMath 页面。任何一步都不得上传 `.intake/`、密钥、真实 术语库或课程原始材料。 + +## Adding later packages + +Later packages such as `matlab-runner` are integrated through a reviewed pull +request rather than by rewriting the historical `v0.1.0` release. Validate the +root suite and every affected package suite, inspect the public tree, and merge +before deciding whether a new repository tag is warranted. Static adapter tests +must not be described as MATLAB execution evidence. diff --git a/README.md b/README.md index 041733a..3dde6b4 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ runtime behavior. | Tool | Purpose | Status | | --- | --- | --- | | [`math-glossary`](skills/math-glossary/) | Build, review, back up, import, export, and maintain bilingual mathematical glossaries | Released in [`v0.1.0`](https://github.com/VeryMath/AI4Math-MathTool/releases/tag/v0.1.0) under the MIT License | +| [`matlab-runner`](skills/matlab-runner/) | Route explicit MATLAB tasks through an available MATLAB MCP server with execution, testing, and numerical-validation evidence | Review candidate; unreleased | Each package keeps its own workflow, dependencies, provenance, normalization record, and tests. Course origin is recorded as metadata; it does not determine @@ -21,6 +22,7 @@ repository boundaries. ```bash python3 -m unittest discover -s tests -v python3 -m unittest discover -s skills/math-glossary/tests -v +python3 -m unittest discover -s skills/matlab-runner/tests -v ``` ## Publication status @@ -33,6 +35,11 @@ instruction and third-party review are recorded in The original submission remains in a teacher-controlled, Git-ignored archive. Contributor names use the course-wide English romanization convention. +`matlab-runner` is a separate review candidate contributed by `njustar2002`. +Its publication authorization and source checksum are recorded in +[`skills/matlab-runner/PROVENANCE.yaml`](skills/matlab-runner/PROVENANCE.yaml). +It is not included in the historical `v0.1.0` release. + See [THIRD_PARTY.md](THIRD_PARTY.md) for dependency and material boundaries, [SECURITY.md](SECURITY.md) for safe operation, and [PUBLISHING.md](PUBLISHING.md) for the remote publication procedure. diff --git a/README.zh-CN.md b/README.zh-CN.md index 7dac900..55957bd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -10,6 +10,7 @@ AI4Math-MathTool 是 VeryMath 用于承载独立数学工具的仓库。进入 | 工具 | 用途 | 状态 | | --- | --- | --- | | [`math-glossary`](skills/math-glossary/) | 构建、审阅、备份、导入、导出和维护双语数学术语库 | 已按 MIT 许可发布 [`v0.1.0`](https://github.com/VeryMath/AI4Math-MathTool/releases/tag/v0.1.0) | +| [`matlab-runner`](skills/matlab-runner/) | 把明确的 MATLAB 任务路由到已配置的 MATLAB MCP Server,并记录执行、测试和数值验证证据 | Review Candidate,尚未发布 Release | 每个 package 独立保留自己的工作流、依赖、来源、规范化记录和测试。课程来源只作为 元数据,不作为代码仓库的职责边界。 @@ -19,6 +20,7 @@ AI4Math-MathTool 是 VeryMath 用于承载独立数学工具的仓库。进入 ```bash python3 -m unittest discover -s tests -v python3 -m unittest discover -s skills/math-glossary/tests -v +python3 -m unittest discover -s skills/matlab-runner/tests -v ``` ## 发布状态 @@ -30,6 +32,11 @@ python3 -m unittest discover -s skills/math-glossary/tests -v 原始提交仍单独保存在教师控制、Git 忽略的档案目录;贡献者姓名统一按课程名册转写为 英文 `Given-name Surname`。 +`matlab-runner` 是由 `njustar2002` 贡献的独立 Review Candidate,发布授权与 +原始文件校验值记录在 +[`skills/matlab-runner/PROVENANCE.yaml`](skills/matlab-runner/PROVENANCE.yaml)。 +它不属于历史 `v0.1.0` Release。 + 依赖与材料边界见 [THIRD_PARTY.md](THIRD_PARTY.md),安全说明见 [SECURITY.md](SECURITY.md),创建远端、运行 CI 与发布版本的步骤见 [PUBLISHING.md](PUBLISHING.md)。 diff --git a/SECURITY.md b/SECURITY.md index 85c3c29..8bd9bbc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -15,3 +15,13 @@ AI enrichment sends selected terms to the endpoint configured by the user. Review the endpoint's privacy and data-retention terms before using real research data. Never store API keys in a glossary, command history, log, or repository file. + +The MATLAB runner may ask a connected MATLAB MCP server to execute code or +write files. Review generated or third-party `.m` files before execution, use +the narrowest requested capability, and keep destructive file operations under +explicit user control. The package does not store MATLAB licenses, VPN +credentials, MCP secrets, or local executable paths. Keep those values in the +user's local client or environment configuration and never commit them. +Do not expose one MATLAB MCP Server as a shared multi-user service; the +upstream licensing guidance requires contacting MathWorks for shared or +centralized deployments. diff --git a/SKILL.md b/SKILL.md index c4b2fa5..e255bde 100644 --- a/SKILL.md +++ b/SKILL.md @@ -10,6 +10,7 @@ Route each request to the narrowest matching package: | Request | Package | | --- | --- | | Build, update, query, back up, import, export, or review a bilingual mathematical glossary | `skills/math-glossary/SKILL.md` | +| Explicitly use MATLAB, operate on `.m` files, or obtain MATLAB-specific execution, test, toolbox, license, or numerical-validation evidence | `skills/matlab-runner/SKILL.md` | Before running a package: diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index fe29137..3455635 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -25,3 +25,26 @@ User-supplied dictionaries, corpora, papers, scans, PDFs, spreadsheets, and other source materials are not part of this repository. Users are responsible for confirming that they may process and redistribute any derived glossary content. + +## MATLAB Runner boundaries + +`skills/matlab-runner` contains routing instructions, references, tests, and +small original MATLAB examples. It does not vendor MATLAB, MathWorks products, +the MATLAB MCP Server, the MATLAB Agentic Toolkit, OpenCode, or another coding +agent. It is a community adapter and is not an official MathWorks or OpenCode +package. + +The following software, when selected by a user, is installed separately and +remains governed by its upstream terms: + +| Dependency | Purpose | Upstream terms | +| --- | --- | --- | +| [MATLAB](https://www.mathworks.com/products/matlab.html) and optional toolboxes | Numerical runtime and product-specific functions | MathWorks product license selected by the user or institution | +| [MATLAB MCP Server](https://github.com/matlab/matlab-mcp-server#licensing-and-usage) | Expose MATLAB execution capabilities to an MCP client | Upstream `LICENSE.md`; use only with MATLAB, do not share one server among multiple users, and contact MathWorks for shared or centralized use | +| [MATLAB Agentic Toolkit](https://github.com/matlab/matlab-agentic-toolkit) | Optional upstream agentic components | Upstream `LICENSE.md`, including the MathWorks-products use condition | +| [OpenCode](https://github.com/anomalyco/opencode) | One optional coding-agent client; not required by this package | MIT | + +The repository MIT license applies to the normalized `matlab-runner` package, +not to separately installed MATLAB or MathWorks components. Users must provide +their own valid MATLAB installation and license and follow each dependency's +terms. diff --git a/skills/matlab-runner/CHANGELOG.md b/skills/matlab-runner/CHANGELOG.md new file mode 100644 index 0000000..6e4c6c2 --- /dev/null +++ b/skills/matlab-runner/CHANGELOG.md @@ -0,0 +1,200 @@ +# 版本记录 + +## [0.1.8-verymath-review] - 2026-08-10 + +### Coding agent 通用化 + +- 保留原有触发、安全、错误分类、测试与数值验证流程; +- 去除 OpenCode 固定路径、Windows 配置和固定 MCP 工具前缀; +- 改为由 coding agent 检查当前工具目录,按能力和参数模式选择实际工具名; +- 增加 VeryMath 来源、授权、规范化记录和通用客户端设置说明。 + +## [0.1.8-review] - 2026-08-01 + +### 恢复与超时 + +- 新增 `MCP_TOOL_TIMEOUT`,用于区分 MCP 调用超时与 MATLAB 代码运行错误; +- 超时后恢复成功时必须使用 `success (after recovery)` 并保留超时记录; +- 不从超时本身推断 MATLAB 是否实际执行了代码。 + +### 调用上限 + +- 明确区分硬性调用上限与软性一次调用目标; +- 用户明确规定“最多 N 次”时不得擅自超出,失败后应报告并请求重试许可; +- 软性目标被恢复调用超过时,必须写明约束未满足和真实总次数。 + +### 验证修正 + +- 修正残差、参考解、维度或验收公式后,早期问题记录为恢复后的 `VALIDATION_FAILURE`; +- 后续调用必须重新计算受影响的量; +- 禁止把先前 MATLAB 输出硬编码进修正调用冒充复核; +- ODE 残差必须使用参考解的真实导数项。 + +### 轻量探测 + +- 简单环境探测优先使用顶层语句与直接 `if`/`else`; +- 不为轻量检查引入不必要的本地或嵌套辅助函数。 + +### 交付状态 + +- 汇总老师五项反馈和 v0.1.5–v0.1.7 回归结论; +- 形成当前完整的 v0.1.8 Review Candidate 审阅包。 + + +## [0.1.7-review] - 2026-08-01 + +### 验证基准 + +- 解析解、期望值、参考向量或验收目标在作为真值前必须先通过最小必要验证; +- ODE 参考解至少检查初值/边界条件和定义方程残差; +- 无效基准不得用于解释求解器误差,未能纠正时分类为 `VALIDATION_FAILURE`。 + +### 数组形状 + +- 逐点数值比较前核对采样点、元素数量、方向与尺寸; +- 禁止因行向量与列向量相减触发隐式扩展后继续解释错误结果; +- 预期标量却得到向量时,视为形状诊断信号。 + +### 调用审计 + +- 最终摘要新增 MATLAB MCP 调用总数、成功数和失败数; +- 所有失败、纠正和未采用的调用均计入; +- 禁止把多次调用描述成“一次干净执行”。 + +### 工具箱证据 + +- 产品检测、函数解析和许可证测试必须作为独立证据; +- MATLAB 调用成功本身不算产品检测; +- 许可证测试成功不证明产品或函数已安装并可解析。 + +### 待验证 + +- 增加两道 v0.1.7 定向回归:三层工具箱证据,以及带错误候选解析解的 ODE 基准验证与形状安全。 + +## [0.1.6-review] - 2026-08-01 + +### 完成状态 + +- 完成状态改为描述“用户请求是否完成”,不再把“目标工具箱不可用”等同于任务部分失败; +- 可用性检查成功但依赖不可用时,使用 `Completion status: success`,并单独报告 `TOOLBOX_UNAVAILABLE`。 + +### 错误恢复 + +- 任一失败的 MATLAB MCP 调用都必须保留在最终执行记录中; +- 重试后完成任务时,使用 `success (after recovery)`; +- 统一报告恢复错误的类型、阶段、直接证据和修正动作。 + +### 工具克制与数值说明 + +- 禁止在核心计算末尾追加非必要的默认参数探测、试验性 API 或版本诊断; +- 明确 `odeset()` 的空字段表示未显式设置,不是显示异常; +- 默认求解器任务不需要打印确切默认值时,只报告使用了默认设置与实测结果。 + +### 工具箱检查 + +- 目标工具箱任务优先使用一次针对性 MATLAB 调用核对产品、函数和许可证; +- 结论优先表述为“当前 MATLAB 环境可用/不可用”,不在证据不足时断言安装根因。 + +### 待验证 + +- 增加三道 v0.1.6 定向回归:工具箱状态语义、失败后恢复、ODE 干净默认执行。 + +## [0.1.5-review] - 2026-08-01 + +### 触发 + +- 收紧 Skill 触发条件; +- 普通数学、通用编程和不执行的 code-only 请求不再触发; +- 明确 MATLAB 只是可选工具时不得误触发。 + +### 环境检查 + +- 改为渐进式、按需检查; +- 已有 MCP 工具可用时直接执行; +- 特定工具箱任务只检查目标依赖; +- 禁止未经请求自动安装、重装或更新环境。 + +### 可移植性 + +- 三份 reference 改用 `./references/...`; +- 明确相对于加载的 `SKILL.md` 目录解析; +- 不再依赖固定 `.opencode/skills/...` 路径。 + +### 错误与输出 + +- 新增 Skill、MCP、MATLAB 会话、工具箱、代码执行、测试、验证和未解析环境八类错误; +- 新增统一完成摘要,报告真实执行、工具、产物、测试、验证和结果位置。 + +### 交付整理 + +- 增加 v0.1.5 七个定向回归场景; +- 根据作者偏好移除非必要的 `交付说明.md` 与 `MANIFEST.sha256`。 + +### 待验证 + +- v0.1.5 行为调整仍需作者本机 OpenCode/MATLAB 回归。 + +## [0.1.4-review] - 2026-08-01 + +### 定位 + +- 从“稳定版”调整为 **Review Candidate / 可审阅原型**; +- 明确项目是基于官方 MATLAB MCP 基础设施的 OpenCode 自定义 Skill,而非修改 Toolkit 源码。 + +### 结构 + +- 将单一大型 `SKILL.md` 拆为核心 Skill 和三份按需 reference; +- 增加 reference 的懒加载规则; +- 增加 `evaluate_matlab_code` 的绝对 `project_path` 要求。 + +### 安全与配置 + +- 示例权限改为人工确认优先; +- shell 从 `allow` 改为 `ask`; +- 执行类 MATLAB 工具改为 `ask`; +- MCP 可执行文件改用 `` 占位符; +- 安装文档增加实际文件名枚举; +- 澄清 MCP `timeout` 是工具发现超时。 + +### 示例 + +- `matrix_report.m` 改为函数; +- `plot_damped_sine.m` 改为函数,避免覆盖、基础工作区污染和遗留图窗; +- 单元测试扩展为两个测试。 + +### 证据与交付 + +- 增加 `docs/evidence/`; +- 区分直接摘录、会话摘要和待执行回归; +- 增加 `.gitignore`、`NOTICE.md`、官方参考资料和审阅说明; +- 文本统一为 UTF-8 无 BOM、LF; +- ZIP 使用标准 `/` 路径和 UTF-8 文件名; +- 增加 SHA-256 清单。 + +### 待验证 + +- v0.1.4 拆分结构需要在作者环境运行 ODE 与内存两道定向回归。 + +## [0.1.3] - 2026-08-01 + +- 强制使用 MATLAB 官方 `memory` 接口; +- 禁止使用 Java preferences 或 `ArraySizeLimit` 代替内存字段; +- 最终内存烟雾测试通过。 + +## [0.1.2] - 2026-08-01 + +- 要求在首次 MATLAB 工作流工具前加载 Skill; +- 加强工具克制、单位一致性和未测量声明; +- 加强实际代码忠实报告。 + +## [0.1.1] - 2026-08-01 + +- 改进触发描述; +- 禁止 placeholder 和不完整执行代码; +- 区分 ODE 局部容差与全局误差; +- 区分数据存储与峰值工作内存。 + +## [0.1.0] - 2026-08-01 + +- 初始 MATLAB MCP 工具路由 Skill; +- 共享工作区、文件保护、静态检查、测试和资源安全规则。 diff --git a/skills/matlab-runner/NORMALIZATION.md b/skills/matlab-runner/NORMALIZATION.md new file mode 100644 index 0000000..1c4c8c0 --- /dev/null +++ b/skills/matlab-runner/NORMALIZATION.md @@ -0,0 +1,26 @@ +# MATLAB Runner normalization record + +This public review candidate preserves the contributor's trigger boundary, +lightweight preflight, MATLAB implementation and execution flow, unit-testing +rules, numerical validation rules, error taxonomy, and completion summary. + +The following repository-edition changes were made on 2026-08-10: + +- moved the canonical package from a client-specific `.opencode` layout to + `skills/matlab-runner/`; +- replaced fixed OpenCode paths and Windows configuration with a relative, + coding-agent-neutral setup reference; +- replaced fixed MCP tool prefixes with runtime discovery by capability and + argument schema; +- added bilingual package documentation, provenance, tests, and repository + metadata; +- excluded `.DS_Store`, local client configuration, the original archive, and + historical review/evidence files from the public package; +- excluded `FINAL_SUBMISSION_AUDIT.md` because it described a `NOTICE.md` file + that was not present in the submitted archive; +- retained the historical changelog as contributor-supplied context without + treating its review statements as current CI or MATLAB execution evidence. + +The normalization tests check repository shape and adapter portability. MATLAB +was not executed by those Python tests, so this record makes no MATLAB runtime, +license, toolbox, or numerical-success claim. diff --git a/skills/matlab-runner/PROVENANCE.yaml b/skills/matlab-runner/PROVENANCE.yaml new file mode 100644 index 0000000..317f4a3 --- /dev/null +++ b/skills/matlab-runner/PROVENANCE.yaml @@ -0,0 +1,30 @@ +schema_version: 1 +package_id: matlab-runner +submission: + original_filename: matlab-runner-v0.1.8-20260803.zip + sha256: 4e6b2f3c83abffd28148bcc36d4f96906d64be14b282fed9db2d32c1d89ad5f3 + checksum_verified: true + archive_scope: contributor-controlled-source + archive_in_git: false +contributor: + public_display_name: njustar2002 + public_email: njustar2002@vip.163.com +authorization: + source_code_publication: approved + approval_basis: repository-owner-confirmation + approval_date: 2026-08-10 + target_repository: VeryMath/AI4Math-MathTool + target_license: MIT + license_spdx: MIT + third_party_material_review: passed + third_party_review_scope: normalized-public-files +normalization: + edition: verymath-platform-neutral-edition + core_workflow_preserved: true + contributor_diff_confirmation: not-collected +release: + integration_state: review-candidate + publication_state: public-candidate + repository_url: https://github.com/VeryMath/AI4Math-MathTool + initial_release: unreleased + blockers: [] diff --git a/skills/matlab-runner/README.md b/skills/matlab-runner/README.md new file mode 100644 index 0000000..6045372 --- /dev/null +++ b/skills/matlab-runner/README.md @@ -0,0 +1,58 @@ +# MATLAB Runner + +[中文说明](README.zh-CN.md) + +`matlab-runner` is a lightweight adapter for coding agents that need to use an +available MATLAB MCP server. It defines when MATLAB should be invoked, how to +choose the narrowest exposed capability, and how to report execution, tests, +and numerical validation honestly. + +The package is a review candidate based on contributor version `v0.1.8`. It is +not an official MathWorks package and does not bundle MATLAB, a MATLAB license, +the MATLAB MCP Server, or the MATLAB Agentic Toolkit. + +## Author + +- Original author: `njustar2002` +- GitHub email: `njustar2002@vip.163.com` + +## When it runs + +The adapter is loaded only when the user explicitly requests MATLAB, the task +operates on MATLAB `.m` files, or a MATLAB-specific toolbox, license, session, +or runtime result is genuinely required. Ordinary mathematics and generic +programming tasks do not trigger it merely because MATLAB could be used. + +## Workflow + +1. Check only the environment capability needed for the task. +2. Inspect the coding agent's current tool catalog. +3. Match the required MATLAB MCP capability and its argument schema. +4. Implement or inspect the MATLAB code. +5. Execute through the available MATLAB MCP server when execution is requested. +6. Run MATLAB unit tests and numerical checks when applicable. +7. Report the actual tool calls, files, tests, validation criteria, and limits. + +Tool names are not hard-coded. The coding agent must use the actual exposed +name for a capability such as `evaluate_matlab_code`, whether its client shows +that name bare, with a server prefix, or with a namespace. + +## Setup + +Place this package where the coding agent can load skills, then configure a +compatible MATLAB MCP server in that client. See +[`references/client-setup.md`](references/client-setup.md). The canonical +workflow contains no client-specific installation path. + +## Validation + +The included Python tests validate package structure and portable routing +rules. They do not claim to execute MATLAB: + +```bash +python3 -m unittest discover -s skills/matlab-runner/tests -v +``` + +See [`PROVENANCE.yaml`](PROVENANCE.yaml) for authorship and publication +authorization, and [`NORMALIZATION.md`](NORMALIZATION.md) for the public +edition changes. diff --git a/skills/matlab-runner/README.zh-CN.md b/skills/matlab-runner/README.zh-CN.md new file mode 100644 index 0000000..3eded43 --- /dev/null +++ b/skills/matlab-runner/README.zh-CN.md @@ -0,0 +1,54 @@ +# MATLAB Runner + +[English](README.md) + +`matlab-runner` 是一个供各类 coding agent 使用的轻量 MATLAB +适配器。它不代替 MATLAB 或 MATLAB MCP Server,而是统一规定:什么时候 +调用 MATLAB、怎样选择当前客户端实际提供的工具,以及怎样如实报告 +执行、测试和数值验证结果。 + +当前是基于作者 `v0.1.8` 整理的 Review Candidate。本项目不是 MathWorks +官方产品,也不捆绑 MATLAB、MATLAB 许可证、MATLAB MCP Server 或 MATLAB +Agentic Toolkit。 + +## 作者 + +- 原作者:`njustar2002` +- GitHub 邮箱:`njustar2002@vip.163.com` + +## 触发条件 + +只有用户明确要求使用 MATLAB、任务处理 `.m` 文件,或必须使用 MATLAB +工具箱、许可证、会话或真实运行结果时才调用。普通数学和通用编程 +任务不会因为“MATLAB 也能做”而误触发。 + +## 执行流程 + +1. 只预检当前任务真正需要的环境能力。 +2. 检查 coding agent 当前的工具目录。 +3. 按 MATLAB MCP 能力和参数模式匹配实际工具。 +4. 实现或检查 MATLAB 代码。 +5. 需要执行时,通过已配置的 MATLAB MCP Server 真实运行。 +6. 按需完成 MATLAB 单元测试和数值验证。 +7. 统一报告调用、代码、文件、测试、验证标准和局限。 + +工具名不写死。coding agent 应该根据当前客户端实际暴露的名称调用 +`evaluate_matlab_code` 等逻辑能力,无论客户端展示的是裸名、服务器前缀 +还是命名空间。 + +## 配置 + +把这个 package 放到 coding agent 能加载 Skill 的位置,再按该客户端的 +方式配置兼容的 MATLAB MCP Server。具体见 +[`references/client-setup.md`](references/client-setup.md)。核心 Skill 不依赖固定安装路径。 + +## 验证 + +下列测试只验证 package 结构和跨客户端路由规则,不代表 MATLAB 已执行: + +```bash +python3 -m unittest discover -s skills/matlab-runner/tests -v +``` + +作者与发布授权见 [`PROVENANCE.yaml`](PROVENANCE.yaml),公开版整理记录见 +[`NORMALIZATION.md`](NORMALIZATION.md)。 diff --git a/skills/matlab-runner/SKILL.md b/skills/matlab-runner/SKILL.md new file mode 100644 index 0000000..e10c515 --- /dev/null +++ b/skills/matlab-runner/SKILL.md @@ -0,0 +1,203 @@ +--- +name: matlab-runner +description: "Use when a user explicitly requests MATLAB execution or verification, asks to inspect, test, generate, or modify MATLAB .m files, or needs MATLAB-specific functions, toolboxes, licenses, sessions, or environment evidence." +--- + +# MATLAB Runner + +Use an available MATLAB MCP Server as the execution layer. This skill is a lightweight, coding-agent-neutral routing, safety, diagnosis, and reporting layer; it does not replace specialized MATLAB skills or MATLAB's own testing, profiling, and resource-management facilities. + +## 1. Strict trigger boundary + +Load `matlab-runner` before the first workflow tool call only when at least one of these conditions is met: + +- the user explicitly asks to use MATLAB, run MATLAB, or obtain a MATLAB-verified result; +- the task asks to execute, inspect, test, generate, or modify a MATLAB `.m` file; +- the task genuinely requires MATLAB-specific functions, toolboxes, files, licenses, shared-session state, or environment evidence. + +Do not load or run MATLAB merely because MATLAB could solve the problem. In particular, do not trigger for: + +- ordinary mathematical derivations or calculations that do not request MATLAB evidence; +- generic programming questions; +- another language's code or toolchain; +- MATLAB code-only requests that explicitly require no execution, file creation, inspection, or tools; +- actions restricted to another named tool. + +Respect boundaries such as “do not run,” “do not modify,” “do not create files,” “do not overwrite,” and “static analysis only.” + +When the skill applies, the order is: + +```text +Skill "matlab-runner" +→ optional task-specific reference +→ required MATLAB MCP or project tool +``` + +Never call MATLAB first and load the skill afterward. + +## 2. Resolve references relative to this skill + +The detailed rules are stored beside this file. Resolve every reference path relative to the directory containing this `SKILL.md`; never assume a fixed client-specific installation path. + +Read only the relevant file: + +- numerical accuracy, ODEs, residuals, statistics, complex output, or reproducible randomness: `./references/numerical-safety.md` +- memory, large arrays, Monte Carlo, batching, runtime, or output-volume assessment: `./references/resource-safety.md` +- file creation, name collisions, shared-session protection, test-file naming, or recovery after errors: `./references/file-session-safety.md` + +Do not load all references by default. Do not inspect unrelated project files merely to prepare an answer. If the client cannot resolve a required relative reference, keep the core safety rules below and report the limitation instead of guessing another installation path. + +## 3. Use progressive, lightweight environment checks + +Do not run a full MATLAB, MCP, version, license, and toolbox preflight before every task. + +- If the required MATLAB MCP tool is already available, use the narrowest tool directly. +- A successful tool call is sufficient evidence that the required chain worked for that call; do not repeat installation or connectivity checks afterward. +- For ordinary base-MATLAB work, do not call toolbox detection. +- If a task depends on a named toolbox, function, or license, check only that requirement. Prefer one targeted lightweight MATLAB call that collects three genuinely independent evidence channels when relevant: product detection such as `ver`, function resolution such as `which` or `exist`, and the named license test. A successful MATLAB call is not product-detection evidence, and `license('test',...) == 1` does not prove that the product or function is installed and resolvable. Use broad toolbox detection only when the user explicitly asks for an installed-toolbox or version list, or when targeted evidence is insufficient. +- Describe availability in the current MATLAB environment. If product, function, and license evidence disagree, report the conflict and the practical usability of the required function; do not invent the underlying installation cause. Do not claim a product is definitively uninstalled, misconfigured, or unlicensed unless the returned evidence supports that exact cause. +- If a call fails, perform only the smallest next check needed to locate the failing layer. +- Keep lightweight probes syntactically simple. Prefer ordinary top-level statements and a direct `if`/`else` over local or nested helper functions when the same evidence can be collected without them. +- Never install, reinstall, update, or reconfigure MATLAB, the MCP Server, Agentic Toolkit, or a toolbox unless the user explicitly asks for environment setup. + +Do not use shell commands as a hidden environment preflight when MATLAB MCP tools are available. + +## 4. Use MATLAB MCP, not a shell substitute + +Use MATLAB MCP tools whenever they are available. Do not invoke MATLAB through PowerShell, bash, a terminal, or `matlab.exe -batch` as an unrequested workaround. + +Never claim that MATLAB ran unless a MATLAB MCP tool returned execution output. If the MCP server is unavailable, report that limitation rather than silently switching execution routes. + +## 5. Discover and choose the narrowest correct tool + +Before the first MATLAB call, inspect the current tool catalog and match capabilities and argument schemas. Coding agents may expose the same MCP capability with a bare name, a server prefix, or a namespace. Use the actual exposed tool name and its returned schema. Do not invent a client-specific prefix. + +Match these logical capabilities rather than literal names: + +| Task | MATLAB MCP capability | +|---|---| +| Short code, direct calculations, targeted lightweight checks | `evaluate_matlab_code` | +| Run an existing MATLAB program | `run_matlab_file` | +| Run a MATLAB unit-test file | `run_matlab_test_file` | +| Static Code Analyzer inspection only | `check_matlab_code` | +| MATLAB version and installed toolboxes | `detect_matlab_toolboxes` | + +Do not use a general execution capability when a dedicated test, static-analysis, or toolbox capability fits. If multiple tools appear to match, compare their descriptions and parameter schemas before choosing. If no MATLAB MCP capability is exposed, report `MCP_UNAVAILABLE`; do not fall back to a shell command merely because a MATLAB executable exists. + +Follow the selected tool's actual argument schema. When an evaluation capability accepts `project_path`, pass the absolute path of the intended project directory. For file and test capabilities, pass an absolute `script_path` when that parameter is present. Never redirect MATLAB to an unrelated folder. + +## 6. Execution discipline and core safety + +Before a MATLAB call: + +1. identify the requested result, precision, file effects, named toolbox needs, output size, and safety constraints; +2. send the smallest complete code block that can answer the request; +3. decide what MATLAB must print to support every planned conclusion; +4. for file writes, check the destination first; +5. for expensive tasks, assess feasibility before allocating data; +6. before treating an analytical solution, expected value, reference dataset, or acceptance target as ground truth, validate the reference itself with the cheapest relevant condition or invariant. + +Never send placeholders, unfinished ellipses, pseudocode, dummy loops, no-op assignments, or incomplete expressions to MATLAB. + +Keep the requested computation separate from optional diagnostics. Do not append exploratory API calls, default-value probes, version checks, or other diagnostics to a core computation unless the user requested them or they are required to interpret the result. Avoid dumping large arrays or logs; return summaries unless the full output is necessary. + +Core rules: + +- Preserve the shared MATLAB session. Do not use `clear`, `clearvars`, `clear all`, `clc`, `close all`, or restart MATLAB unless explicitly requested and necessary. +- Do not overwrite, delete, rename, or modify an existing file without explicit authorization. +- Do not create a large array or begin an expensive simulation when a lightweight assessment already shows serious risk. +- Do not automatically run a safer alternative when the user asked only for an assessment or unexecuted example code. +- Treat official environment output, theoretical calculations, and unmeasured estimates as different evidence categories. +- Before pointwise numerical comparison, verify that sample points, lengths, orientation, and dimensions correspond. Do not allow accidental row-versus-column implicit expansion to create a matrix of cross-comparisons. +- Keep a human in the loop for consequential execution or file changes when permissions request approval. + +## 7. Classify failures by layer + +Use the most specific supported category; do not guess. If evidence does not distinguish the layer, use `ENVIRONMENT_UNRESOLVED` and state what remains unknown. + +| Error type | Use when | +|---|---| +| `SKILL_NOT_LOADED` | A MATLAB workflow required this skill, but it was not loaded or the load failed; stop before further MATLAB work. | +| `MCP_UNAVAILABLE` | The MATLAB MCP server or required MCP tool is not configured, exposed, or connected. | +| `MCP_TOOL_TIMEOUT` | A dispatched MATLAB MCP call exceeded the client or tool timeout. Do not infer from the timeout alone whether MATLAB executed the code. | +| `MATLAB_SESSION_UNAVAILABLE` | The MCP layer responds, but no usable MATLAB process or shared session is available. | +| `TOOLBOX_UNAVAILABLE` | A required toolbox, function, or license is unavailable. State which condition was actually checked. | +| `MATLAB_EXECUTION_ERROR` | MATLAB accepted the execution request but the code produced a syntax or runtime error. | +| `MATLAB_TEST_FAILURE` | The MATLAB test tool ran successfully, but one or more tests failed or were incomplete. | +| `VALIDATION_FAILURE` | MATLAB execution succeeded, but the result failed the requested numerical, file, or acceptance criterion. | +| `ENVIRONMENT_UNRESOLVED` | Available evidence is insufficient to distinguish MCP, session, installation, or another environment layer. | + +For an error, report: + +- error type; +- stage where it occurred; +- direct evidence from the tool result; +- the smallest reasonable next action. + +Do not call a failed test an execution error, and do not call a numerically unacceptable result a MATLAB runtime error. A reference solution that fails its initial condition, boundary condition, defining equation, or another required invariant makes the intended numerical comparison invalid; classify the unresolved task as `VALIDATION_FAILURE`, even when MATLAB executed without a runtime error. + +A failed MATLAB MCP call remains part of the execution history even if a later retry succeeds. When recovery completes the requested task, report `Completion status: success (after recovery)` and include the recovered error type, stage, direct evidence, and corrective action. Never replace the failed call with only the successful retry in the final account. + +Classify a recovered MCP timeout as `MCP_TOOL_TIMEOUT`; do not silently relabel it as an ordinary success. If a validation formula, reference check, residual, dimension check, or acceptance test is corrected after a prior successful MATLAB run, disclose the earlier issue as a recovered `VALIDATION_FAILURE`. + +Respect call budgets: + +- An explicit maximum such as “at most one MATLAB call” or “do not call again” is a hard constraint. If the available call fails or produces an invalid validation result, do not exceed the limit; report the task as failed or partial and ask for permission before retrying. +- A preference or target such as “try to do this in one call” may be exceeded only by the smallest recovery call needed for correctness. Report the real total and state that the preferred call count was not met. +- Do not retry merely to improve wording, formatting, or optional diagnostics. + +## 8. Evidence and faithful reporting + +After execution: + +1. compare the answer with the actual returned output; +2. check labels, units, formulas, filenames, function names, and summary values for internal consistency; +3. separate MATLAB output from interpretation and optional suggestions; +4. never invent values, license states, runtime, peak memory, accuracy, or success claims; +5. count every MATLAB MCP call that was dispatched, including failed, corrective, and discarded calls. Do not describe only the final successful call as a single execution when earlier calls occurred. + +When the user asks for the code actually sent to MATLAB, reproduce the exact relevant MCP payload. Include failed, timed-out, or partially executed calls when they affected the result. Do not delete a failing line and present the remainder as the complete executed code. If comments or sections are omitted, label the result as an excerpt or as executable statements rather than verbatim code. + +A corrective MATLAB call must recompute every quantity whose validity depends on the corrected formula, reference, shape, or condition. Never hard-code values observed in an earlier call into a follow-up call as a substitute for recomputation or verification. + +When the user asks to run faulty code unchanged, run it unchanged, return the genuine MATLAB error, and keep any repair separate. + +## 9. Unified completion summary + +After any task that invokes MATLAB, checks the MATLAB environment, runs tests, or creates or modifies files, end with a concise completion summary. Use natural wording and omit genuinely inapplicable lines, but always include `Completion status` and `MATLAB execution`. + +Interpret completion status by whether the user's requested task was completed, not by whether the checked dependency was available: + +- `success`: the requested calculation, inspection, availability check, or file task was completed; +- `success (after recovery)`: the task was completed after one or more disclosed failed calls; +- `partial`: only part of the requested deliverables was completed; +- `failed`: the requested core task was not completed. + +For example, a successful check that proves `fmincon` is unavailable has `Completion status: success` and `Error type or limitation: TOOLBOX_UNAVAILABLE`; the unavailable dependency does not make the inspection itself partial. + +Recommended fields: + +```text +Completion status: success / success (after recovery) / partial / failed +MATLAB execution: yes / no +MATLAB tool calls: total N; successful S; failed F +Tools used: ... +Code or files created/modified: ... +Tests: passed / failed / not run +Validation: criterion and outcome +Result location: absolute path, when applicable +Recovered error: type, stage, evidence, and action, when applicable +Constraint compliance: met / not met, when the user imposed a tool-call or execution limit +Error type or limitation: ..., when applicable +``` + +The summary must make clear: + +- what code or files were produced or changed; +- whether MATLAB actually ran and the truthful total number of MATLAB MCP calls; +- whether tests ran and passed; +- what numerical or file validation standard was used; +- where any result was saved; +- whether an explicit call-count or execution constraint was met. + +Do not claim a file was generated when only inline code was shown. Do not relabel a three-call recovery as a one-call task merely because only the last call produced the accepted result. Return exact executed code only when requested; the completion summary is not a substitute for the actual output or error. diff --git a/skills/matlab-runner/examples/README.md b/skills/matlab-runner/examples/README.md new file mode 100644 index 0000000..f5bcbc9 --- /dev/null +++ b/skills/matlab-runner/examples/README.md @@ -0,0 +1,33 @@ +# 示例说明 + +三个示例都避免静默覆盖或无必要污染共享基础工作区。 + +## 矩阵报告 + +在 MATLAB 中: + +```matlab +A = [1 2 3; 0 1 4; 5 6 0]; +report = matrix_report(A); +``` + +在 MCP 中使用实际暴露的 `evaluate_matlab_code` 能力;如果其参数包含 +`project_path`,传入项目绝对路径。 + +## 无覆盖绘图 + +```matlab +outputPath = plot_damped_sine(pwd); +``` + +如果 `damped_sine.png` 已存在,会使用 `damped_sine_1.png` 等第一个可用名称。函数创建不可见图窗,并只关闭自己创建的图窗。 + +## 单元测试 + +使用 coding agent 当前工具目录中实际暴露的专用能力运行: + +```text +run_matlab_test_file +``` + +`script_path` 应是 `test_vector_norm.m` 的绝对路径。预期为 2 个测试通过。 diff --git a/skills/matlab-runner/examples/matrix_report.m b/skills/matlab-runner/examples/matrix_report.m new file mode 100644 index 0000000..4429467 --- /dev/null +++ b/skills/matlab-runner/examples/matrix_report.m @@ -0,0 +1,28 @@ +function report = matrix_report(A) +%MATRIX_REPORT Compute and print basic matrix diagnostics. +% REPORT = MATRIX_REPORT(A) returns rank, determinant (for square A), +% and the 2-norm condition number. All variables remain local. + +validateattributes(A, {'numeric'}, {'2d', 'nonempty'}, mfilename, 'A'); + +report = struct(); +report.Size = size(A); +report.Rank = rank(A); + +if ismatrix(A) && size(A, 1) == size(A, 2) + report.Determinant = det(A); +else + report.Determinant = NaN; +end + +report.ConditionNumber2 = cond(A); + +fprintf('size = %d-by-%d\n', report.Size(1), report.Size(2)); +fprintf('rank = %d\n', report.Rank); +if isnan(report.Determinant) + fprintf('determinant = not defined for a non-square matrix\n'); +else + fprintf('determinant = %.8g\n', report.Determinant); +end +fprintf('2-norm condition number = %.8g\n', report.ConditionNumber2); +end diff --git a/skills/matlab-runner/examples/plot_damped_sine.m b/skills/matlab-runner/examples/plot_damped_sine.m new file mode 100644 index 0000000..498fd4c --- /dev/null +++ b/skills/matlab-runner/examples/plot_damped_sine.m @@ -0,0 +1,64 @@ +function outputPath = plot_damped_sine(outputDir) +%PLOT_DAMPED_SINE Export a damped-sine plot without overwriting files. +% OUTPUTPATH = PLOT_DAMPED_SINE(OUTPUTDIR) writes damped_sine.png, or the +% first available suffixed name. The function uses an invisible figure, +% closes only that figure, and does not pollute the base workspace. + +if nargin < 1 + outputDir = pwd; +end + +if ~(ischar(outputDir) || (isstring(outputDir) && isscalar(outputDir))) + error('plot_damped_sine:InvalidOutputDir', ... + 'outputDir must be a character vector or string scalar.'); +end +outputDir = char(outputDir); + +if ~isfolder(outputDir) + error('plot_damped_sine:InvalidFolder', ... + 'Output directory does not exist: %s', outputDir); +end + +preferredPath = fullfile(outputDir, 'damped_sine.png'); +outputPath = firstAvailablePath(preferredPath); + +x = linspace(0, 20, 1000); +y = exp(-0.15 * x) .* sin(2 * x); + +fig = figure('Visible', 'off'); +cleanupFigure = onCleanup(@() close(fig)); %#ok +ax = axes('Parent', fig); + +plot(ax, x, y, 'LineWidth', 1.5); +title(ax, 'Damped Sine: y = exp(-0.15x) sin(2x)'); +xlabel(ax, 'x'); +ylabel(ax, 'y'); +grid(ax, 'on'); + +exportgraphics(fig, outputPath); + +if ~isfile(outputPath) + error('plot_damped_sine:ExportFailed', ... + 'Expected output file was not created: %s', outputPath); +end + +fprintf('File path: %s\n', outputPath); +end + +function outputPath = firstAvailablePath(preferredPath) +outputPath = preferredPath; +if ~isfile(outputPath) + return +end + +[folder, name, extension] = fileparts(preferredPath); +index = 1; +while true + candidate = fullfile(folder, sprintf('%s_%d%s', name, index, extension)); + if ~isfile(candidate) + outputPath = candidate; + return + end + index = index + 1; +end +end diff --git a/skills/matlab-runner/examples/test_vector_norm.m b/skills/matlab-runner/examples/test_vector_norm.m new file mode 100644 index 0000000..2d40c7a --- /dev/null +++ b/skills/matlab-runner/examples/test_vector_norm.m @@ -0,0 +1,12 @@ +function tests = test_vector_norm +%TEST_VECTOR_NORM Function-based tests for simple vector lengths. +tests = functiontests(localfunctions); +end + +function testNorm3_4(testCase) +verifyEqual(testCase, norm([3 4]), 5, 'AbsTol', 1e-12); +end + +function testHypot5_12(testCase) +verifyEqual(testCase, hypot(5, 12), 13, 'AbsTol', 1e-12); +end diff --git a/skills/matlab-runner/references/client-setup.md b/skills/matlab-runner/references/client-setup.md new file mode 100644 index 0000000..f9f77b6 --- /dev/null +++ b/skills/matlab-runner/references/client-setup.md @@ -0,0 +1,24 @@ +# Coding-agent setup + +Use this reference only when installing or connecting the skill. + +1. Place the complete `matlab-runner` directory in a location from which the + coding agent can load skills, or register its canonical `SKILL.md` using the + client's supported skill mechanism. +2. Configure a compatible MATLAB MCP Server using that coding agent's MCP + configuration mechanism. Keep executable paths and local MATLAB settings in + client configuration, not in this package. +3. Restart or reload the client if its tool catalog is cached. +4. Confirm that the client exposes the MATLAB capability needed by the task, + such as evaluation, file execution, unit testing, static analysis, or + toolbox detection. +5. Let the coding agent inspect the exposed tools, compare descriptions and + parameter schemas, and call the actual tool name shown by that client. + +Different clients may expose the same capability as a bare name, a +server-prefixed name, or a namespaced name. The adapter intentionally does not +hard-code one spelling or installation directory. + +MATLAB, its license, and the MCP server are separate prerequisites. Do not +install, reconfigure, or replace them automatically unless the user explicitly +requests environment setup. diff --git a/skills/matlab-runner/references/file-session-safety.md b/skills/matlab-runner/references/file-session-safety.md new file mode 100644 index 0000000..38f7716 --- /dev/null +++ b/skills/matlab-runner/references/file-session-safety.md @@ -0,0 +1,86 @@ +# File and Shared-Session Safety Reference + +> Path note: this file is addressed from `SKILL.md` as `./references/file-session-safety.md` and must be resolved relative to the directory containing that loaded `SKILL.md`. + +Load this reference only for tasks involving file creation or modification, name collisions, MATLAB test files, shared-session variables, plots and figures, or recovery after errors. + +## Shared MATLAB session + +Assume the active session may contain valuable user state: + +- variables; +- current folder; +- paths; +- RNG state; +- open figures; +- toolbox or application state. + +Do not use `clear`, `clearvars`, `clear all`, `clc`, `close all`, or restart MATLAB unless the user explicitly requests it and the action is necessary. + +An execution error is not a reason to clear or restart the session. Report the error and preserve unrelated state. + +When cross-call persistence matters, verify it with separate MCP calls. + +## File creation workflow + +Default to non-destructive behavior: + +1. determine the intended absolute path; +2. use MATLAB `isfile`, `isfolder`, or the appropriate test before writing; +3. if the preferred name exists and overwrite is not authorized, select the first unused suffix such as `_1`, `_2`, and so on; +4. create the file once the final path is known; +5. verify that the final file exists; +6. return the actual absolute path. + +Do not overwrite, delete, rename, or modify an existing file without explicit authorization. + +## Test-file naming + +For MATLAB function-based unit tests: + +- the primary test-function name must match the final `.m` filename; +- if a collision changes the filename, change the primary function name too; +- run the file using the exposed `run_matlab_test_file` capability; +- report Passed, Failed, and Incomplete counts from the actual result. + +## Plot and figure safety + +Prefer function-based examples that keep variables local. + +- Use an invisible figure for noninteractive output unless the user requests a visible plot. +- Use `onCleanup` or another reliable mechanism to close only the figure created by the task. +- Do not call `close all` in a shared session. +- Check the destination before exporting. +- Return the actual output path. + +## Static analysis + +Static-analysis requests are read-only: + +- use the exposed `check_matlab_code` capability; +- do not execute the file; +- do not modify the file; +- do not add unrelated project reads unless the user asked for broader inspection. + +## Existing scripts and faulty code + +When the user asks to run an existing file unchanged, use its absolute path and do not rewrite it silently. + +When the user asks to run faulty code unchanged: + +1. run it unchanged; +2. return the genuine MATLAB error; +3. keep any repair separate and clearly labeled; +4. do not present the repaired run as the original result. + +## Verification after writes + +For important artifacts, verify more than existence when appropriate: + +- reload MAT-file contents; +- check expected variables or dimensions; +- verify image or report path; +- run the generated test file; +- compare a sentinel value when testing overwrite protection. + +Use the narrowest verification required by the task. Do not add unrelated file inspection. diff --git a/skills/matlab-runner/references/numerical-safety.md b/skills/matlab-runner/references/numerical-safety.md new file mode 100644 index 0000000..c01ac00 --- /dev/null +++ b/skills/matlab-runner/references/numerical-safety.md @@ -0,0 +1,126 @@ +# Numerical Safety Reference + +> Path note: this file is addressed from `SKILL.md` as `./references/numerical-safety.md` and must be resolved relative to the directory containing that loaded `SKILL.md`. + +Load this reference only for tasks involving numerical accuracy, ODEs, roots and residuals, statistics, complex output, conditioning, convergence, or reproducible randomness. + +## General evidence rules + +- Preserve complex values and display real and imaginary parts when interpretation depends on them. +- Equal real parts do not imply repeated roots. +- Use unrounded values for validation and round only for presentation. +- Do not infer multiplicity, conditioning, convergence, stability, or reliability without a calculation or a clearly stated theorem. +- Never invent a value that MATLAB did not return. + +## Validate the reference before measuring error + +An analytical solution, expected answer, reference vector, or benchmark is not automatically ground truth merely because it was supplied in the prompt or produced by the agent. Before using it to judge MATLAB output, apply the cheapest decisive checks. + +For an ODE reference solution, check at least: + +1. the initial or boundary condition at full precision; +2. the defining differential equation through an analytical residual or another justified check; +3. any domain, parameter, or branch condition that affects correspondence. + +If a candidate reference fails one of these checks, do not use it to compute or interpret solver error. Either derive and validate a corrected reference, or report `VALIDATION_FAILURE` and stop the accuracy claim. A MATLAB call that runs successfully does not make an invalid reference valid. + +For pointwise comparisons: + +- use the same requested sample points for numerical and reference values; +- make orientation explicit, preferably with a column time vector such as `tq = (0:0.1:5).'`; +- verify equal element counts and matching dimensions before subtraction; +- use an assertion such as `assert(isequal(size(yNumerical),size(yReference)))` when shape mistakes would invalidate the result; +- do not rely on implicit expansion between a column and a row; +- using `(:)` is acceptable only after confirming that both vectors refer to the same ordered sample points. + +A vector returned by `max` where a scalar was expected is evidence of a shape or dimension problem. Diagnose it; do not reinterpret the vector as a running maximum or another quantity without proof. + +## Differential-equation residuals + +For an equation such as `y' = f(t,y)`, the residual must contain the derivative of the proposed reference solution. Define or derive a separate derivative expression such as `yPrime(t)` and evaluate the residual in the equation's actual form. + +For example, for `y' = -2y + sin(t)`, use: + +```matlab +residual = yPrime(t) + 2*yExact(t) - sin(t); +``` + +Do not substitute `yExact(t)` where `yPrime(t)` is required. A residual formula that uses the wrong mathematical quantity invalidates the validation even when MATLAB executes it successfully. + +When correcting a residual or reference formula: + +- classify the earlier check as a recovered `VALIDATION_FAILURE`; +- recompute the residual from the corrected derivative or formula; +- recompute any reported quantities that depended on the invalid check; +- do not insert previously observed numerical outputs as constants in the corrective call. + +## Residuals and backward error + +An absolute residual alone may be misleading when the problem is scaled. + +- Use a normalization appropriate to the equation or polynomial. +- Do not compare an unnormalized residual directly with machine epsilon and announce stability. +- State the quantity actually computed: absolute residual, relative residual, normalized backward error, or another measure. +- Do not use a small residual as proof of good conditioning. + +## Statistics + +State the convention when it matters. + +- `std(A,0,'all')` uses `N-1` normalization when applicable. +- `std(A,1,'all')` uses `N` normalization. +- If NaN values are excluded by indexing, say that they were filtered before `mean` or `std`; do not say the functions ignored NaN automatically unless an omission option was actually used. + +## Random experiments + +For requested reproducibility: + +1. set an explicit RNG seed; +2. report the seed and generator when relevant; +3. verify reproducibility when requested, for example by regenerating and comparing with `isequal`. + +Do not change the shared session's RNG state unnecessarily. If preserving the prior RNG state matters, save and restore it explicitly. + +## ODE solver tolerances + +For `ode45` and other adaptive solvers: + +- Treat `RelTol` and `AbsTol` as controls on estimated local error, not as guaranteed bounds on the global maximum error. +- Do not say an observed global error is “below the tolerance threshold” solely because it is numerically smaller than `RelTol`. +- If an exact solution exists, validate that solution against the initial or boundary data and the differential equation before computing the observed error. +- To assess improvement from tighter tolerances, rerun with explicit tighter values and compare measured errors. +- State whether values came from requested output points or interpolation. +- When the input time vector contains requested points, call them requested output points, not the solver's internal adaptive steps. +- Do not invent a rule such as “the global error is approximately a fixed multiple of `RelTol`” without a derivation or measurement supporting that specific claim. + +## Default settings and optional diagnostics + +When the user asks to compare a solver's default settings with explicit settings: + +- use the solver without explicit option overrides for the default run; +- do not query or print exact default option values unless the user requested them or they are necessary to answer the task; +- keep optional option inspection out of the core computation call; +- if an optional diagnostic is attempted and fails, disclose it as a recovered `MATLAB_EXECUTION_ERROR` when a later call completes the task. + +An empty field returned by `odeset()` means that the option was not explicitly set in that options structure. It is not evidence of a display problem. Do not infer or state a specific default value from an empty field. If the exact default is not required, say that the solver used its default settings and report the measured result. + +## Complex eigenvalues and roots + +- Print complex values with enough precision to preserve conjugate structure. +- Do not collapse a complex-conjugate pair into duplicate real numbers. +- Check algebraic multiplicity separately from numerical closeness. +- If the task concerns eigenvectors or conditioning, compute the relevant quantities rather than inferring them from eigenvalues alone. + +## Numerical claims checklist + +Before answering, verify: + +- the formula matches the code and the validation reference itself has passed the required initial, boundary, equation, or invariant checks; +- the reported maximum is the maximum of the stated values; +- units and normalization are named; +- default and tightened settings are not mixed; +- sample points, element counts, orientation, and dimensions match, with no accidental implicit expansion; +- rounded display values are not reused for validation; +- every factual numerical conclusion is supported by actual output or a transparent calculation; +- every differential-equation residual uses the actual derivative term required by the equation; +- a corrective call recomputed affected quantities instead of hard-coding values from an earlier call. diff --git a/skills/matlab-runner/references/resource-safety.md b/skills/matlab-runner/references/resource-safety.md new file mode 100644 index 0000000..7df072c --- /dev/null +++ b/skills/matlab-runner/references/resource-safety.md @@ -0,0 +1,125 @@ +# Resource Safety Reference + +> Path note: this file is addressed from `SKILL.md` as `./references/resource-safety.md` and must be resolved relative to the directory containing that loaded `SKILL.md`. + +Load this reference only for tasks involving memory, large arrays, Monte Carlo simulations, batching, long runtimes, large outputs, spectral computations, or resource-feasibility decisions. + +## Assess before allocating + +Before a potentially large computation: + +1. calculate the minimum primary-data size; +2. identify the computational order and output volume; +3. determine whether MATLAB environment measurements are required; +4. distinguish primary storage from additional working memory; +5. keep runtime feasibility separate from memory feasibility. + +Do not create data or start a computation when a lightweight assessment already indicates serious risk of exhausting memory, freezing the system, or producing impractically large work. + +## Exact storage versus peak working memory + +Exact storage may be reported only for arrays whose element count and data type are known. + +For example, `N` two-dimensional double points require at least: + +```text +N × 2 × 8 bytes +``` + +This is the coordinate storage only. Peak working memory may additionally include: + +- temporary arithmetic arrays; +- logical masks; +- copies created by expression evaluation; +- solver workspaces; +- returned results; +- file buffers and serialization overhead. + +Do not label one input array or one batch's coordinates as the total peak memory. Unless measured or rigorously bounded, describe peak working memory as unmeasured and larger than the known primary storage. + +## Official Windows MATLAB memory inspection + +When the user asks for `MaxPossibleArrayBytes`, `MemAvailableAllArrays`, `MemUsedMATLAB`, or a Windows MATLAB memory-safety decision, use the official interface: + +```matlab +m = memory; +``` + +Read these fields from the returned MATLAB structure: + +- `m.MaxPossibleArrayBytes` +- `m.MemAvailableAllArrays` +- `m.MemUsedMATLAB` + +Do not substitute: + +- Java preferences; +- `ArraySizeLimit`; +- undocumented internal APIs; +- registry or environment values; +- static configuration limits. + +`MaxPossibleArrayBytes` is the upper bound MATLAB reports for the largest array it can create at that time. It is not physical RAM. + +Have MATLAB print raw bytes and requested decimal-GB and binary-GiB conversions in the same successful lightweight call. + +If a required field is missing, empty, zero, nonfinite, or implausible: + +1. confirm the official `memory` interface and correct field were used; +2. run one minimal official recheck when safe; +3. if no valid value is obtained, report that the environment measurement is unavailable; +4. do not compute a misleading ratio or infer “unlimited” or “not configured.” + +## Units + +Use consistent labels: + +- decimal GB: bytes divided by `1e9`; +- binary GiB: bytes divided by `2^30`. + +Prefer MATLAB-generated conversions in the same call. Copy the printed values and labels accurately. + +## Batching and Monte Carlo + +A chunked design can make memory usage bounded without making total work small. + +- State the exact coordinate storage for one batch when known. +- State that derived arrays and temporary values add unmeasured overhead unless measured. +- Keep only accumulators when the task does not require retaining all points. +- Do not run demonstration batching code when the user asked only to see it. +- Do not give a concrete per-batch or total runtime without an authorized benchmark or other defensible evidence. +- “Memory-bounded” is not the same as “safe,” “fast,” or “practical.” + +## Large outputs + +Avoid printing or returning complete large arrays. Prefer: + +- dimensions; +- minima and maxima; +- means and standard deviations with stated conventions; +- selected entries; +- residuals; +- counts and checksums; +- saved-file paths when output was explicitly requested. + +## Spectral and sparse alternatives + +Suggest alternatives only when mathematically appropriate: + +- sparse storage for genuinely sparse data; +- operator forms when the algorithm supports them; +- `eigs` for a small subset of eigenvalues; +- chunked accumulation when all samples need not be retained; +- lower precision only when accuracy requirements allow it. + +Do not imply that streaming can recover the complete spectrum of an arbitrary dense matrix, or that sparse storage helps dense random coordinates. + +## Runtime claims + +No concrete runtime estimate is supported merely because a loop count is known. + +If runtime matters: + +- run a small benchmark only with user authorization; +- make clear that extrapolation can be unreliable; +- otherwise state that the total work is large and runtime was not measured. diff --git a/skills/matlab-runner/tests/scenarios/final-acceptance.md b/skills/matlab-runner/tests/scenarios/final-acceptance.md new file mode 100644 index 0000000..90ef8bb --- /dev/null +++ b/skills/matlab-runner/tests/scenarios/final-acceptance.md @@ -0,0 +1,67 @@ +# v0.1.8 最终验收提示词 + +这些提示词用于验证 v0.1.8 的最终恢复和调用预算规则。它们不会被 +Python 结构测试自动执行;本公开 package 也不把历史审阅摘要当作当前 +MATLAB 运行证据。要宣称验收通过,必须在真实 MATLAB 环境中单独运行并保留结果。 + +## 测试 1:硬性一次调用上限 + +```text +请原样使用 MATLAB 执行 A = [1 2; 3];。 + +要求: +1. 第一个工具调用前加载 matlab-runner; +2. 最多只允许一次 MATLAB MCP 调用; +3. 不得修复代码,不得重试; +4. 返回 MATLAB 真实错误; +5. 完成状态为 failed; +6. 错误类型为 MATLAB_EXECUTION_ERROR; +7. 摘要写 MATLAB tool calls: total 1,并说明调用上限已满足。 +``` + +理想行为:一次调用失败后停止,不擅自进行第二次调用。 + +## 测试 2:ODE 单次完整验证 + +```text +请实际使用 MATLAB 求解 y'=-2y+sin(t),y(0)=1,0≤t≤5, +比较 ode45 默认设置和 RelTol=1e-9、AbsTol=1e-12。 + +要求: +1. 加载 matlab-runner; +2. 只读取 numerical-safety reference; +3. 最多一次 MATLAB MCP 调用; +4. 使用正确解析解 y=(6/5)e^(-2t)+(2/5)sin(t)-(1/5)cos(t); +5. 显式定义解析导数 yPrime; +6. 用 yPrime(t)+2*yExact(t)-sin(t) 验证残差; +7. tq、数值解与解析解全部为 51×1; +8. 相减前 assert 尺寸一致; +9. 输出候选初值、正确解初值、最大残差和两组最大绝对误差; +10. 不查询默认容差值,不追加诊断; +11. 摘要写 MATLAB tool calls: total 1,且未生成文件。 +``` + +作者提供的预期误差接近下列数值,实际验收应以当次 MATLAB 输出为准: + +```text +默认设置:2.854e-04 +严格设置:8.824e-11 +``` + +## 测试 3:软性一次调用目标与超时 + +```text +请判断当前 MATLAB 环境是否可以使用 Optimization Toolbox 的 fmincon。 + +尽量在一次针对性调用中完成,但这不是硬性上限。分别检查: +- ver('optim') +- which/exist('fmincon') +- license('test','Optimization_Toolbox') + +若调用超时,可以进行一次最小化恢复。必须: +1. 把超时记录为 MCP_TOOL_TIMEOUT; +2. 最终写 success (after recovery); +3. 报告真实总调用数; +4. 说明“一次调用”的软目标是否满足; +5. 不猜测安装根因。 +``` diff --git a/skills/matlab-runner/tests/test_matlab_runner_package.py b/skills/matlab-runner/tests/test_matlab_runner_package.py new file mode 100644 index 0000000..397b6e5 --- /dev/null +++ b/skills/matlab-runner/tests/test_matlab_runner_package.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +PACKAGE_ROOT = Path(__file__).resolve().parents[1] + + +class MatlabRunnerPackageTests(unittest.TestCase): + def test_canonical_files_exist(self): + for relative_path in [ + "README.md", + "README.zh-CN.md", + "SKILL.md", + "PROVENANCE.yaml", + "NORMALIZATION.md", + "references/client-setup.md", + "references/file-session-safety.md", + "references/numerical-safety.md", + "references/resource-safety.md", + ]: + with self.subTest(path=relative_path): + self.assertTrue((PACKAGE_ROOT / relative_path).is_file()) + + def test_frontmatter_is_portable(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL) + self.assertIsNotNone(match) + keys = { + line.split(":", 1)[0].strip() + for line in match.group(1).splitlines() + if ":" in line + } + self.assertEqual(keys, {"name", "description"}) + + def test_tool_routing_uses_capabilities_not_fixed_prefixes(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + self.assertIn("inspect the current tool catalog", content) + self.assertIn("actual exposed tool name", content) + for forbidden in [ + "matlab_evaluate_matlab_code", + "mcp__matlab__evaluate_matlab_code", + ".opencode/skills", + "C:\\Users", + ]: + with self.subTest(forbidden=forbidden): + self.assertNotIn(forbidden, content) + + def test_references_are_relative_and_present(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + for filename in [ + "file-session-safety.md", + "numerical-safety.md", + "resource-safety.md", + ]: + self.assertIn(f"./references/{filename}", content) + self.assertTrue((PACKAGE_ROOT / "references" / filename).is_file()) + + def test_no_client_configuration_is_vendored(self): + forbidden = {"opencode.json", ".DS_Store", "FINAL_SUBMISSION_AUDIT.md"} + found = [path.name for path in PACKAGE_ROOT.rglob("*") if path.name in forbidden] + self.assertEqual(found, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/registry.yaml b/skills/registry.yaml index 09e1e7f..69df272 100644 --- a/skills/registry.yaml +++ b/skills/registry.yaml @@ -9,3 +9,11 @@ skills: repository: https://github.com/VeryMath/AI4Math-MathTool release: v0.1.0 blockers: [] + - id: matlab-runner + path: skills/matlab-runner + summary: Route explicit MATLAB tasks through available MCP capabilities. + state: review-candidate + publication: public-candidate + repository: https://github.com/VeryMath/AI4Math-MathTool + release: unreleased + blockers: [] diff --git a/tests/test_matlab_runner_integration.py b/tests/test_matlab_runner_integration.py new file mode 100644 index 0000000..a86ab44 --- /dev/null +++ b/tests/test_matlab_runner_integration.py @@ -0,0 +1,145 @@ +from __future__ import annotations + +import re +import unittest +from pathlib import Path + +import yaml + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +PACKAGE_ROOT = REPOSITORY_ROOT / "skills" / "matlab-runner" + + +class MatlabRunnerIntegrationTests(unittest.TestCase): + def test_public_package_has_required_files(self): + required = [ + "README.md", + "README.zh-CN.md", + "SKILL.md", + "PROVENANCE.yaml", + "NORMALIZATION.md", + "CHANGELOG.md", + "references/client-setup.md", + "references/file-session-safety.md", + "references/numerical-safety.md", + "references/resource-safety.md", + "examples/README.md", + "examples/matrix_report.m", + "examples/plot_damped_sine.m", + "examples/test_vector_norm.m", + "tests/scenarios/final-acceptance.md", + ] + for relative_path in required: + with self.subTest(path=relative_path): + self.assertTrue((PACKAGE_ROOT / relative_path).is_file()) + + def test_frontmatter_is_portable_and_discoverable(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL) + self.assertIsNotNone(match) + frontmatter = match.group(1) + keys = { + line.split(":", 1)[0].strip() + for line in frontmatter.splitlines() + if ":" in line + } + self.assertEqual(keys, {"name", "description"}) + self.assertIn("name: matlab-runner", frontmatter) + self.assertRegex(frontmatter, r"description: [\"']?Use when ") + + def test_canonical_skill_is_coding_agent_neutral(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + for forbidden in [ + ".opencode/skills", + "OpenCode routing", + "In OpenCode", + "matlab_evaluate_matlab_code", + "mcp__matlab__evaluate_matlab_code", + "C:\\\\Users", + "WINDIR", + ]: + with self.subTest(forbidden=forbidden): + self.assertNotIn(forbidden, content) + for expected in [ + "inspect the current tool catalog", + "match capabilities and argument schemas", + "actual exposed tool name", + "Do not invent a client-specific prefix", + ]: + with self.subTest(expected=expected): + self.assertIn(expected, content) + + def test_relative_references_are_complete(self): + content = (PACKAGE_ROOT / "SKILL.md").read_text(encoding="utf-8") + for reference in [ + "file-session-safety.md", + "numerical-safety.md", + "resource-safety.md", + ]: + with self.subTest(reference=reference): + self.assertIn(f"./references/{reference}", content) + self.assertTrue((PACKAGE_ROOT / "references" / reference).is_file()) + + def test_provenance_records_authorization_and_author(self): + provenance = yaml.safe_load( + (PACKAGE_ROOT / "PROVENANCE.yaml").read_text(encoding="utf-8") + ) + self.assertEqual(provenance["schema_version"], 1) + self.assertEqual(provenance["package_id"], "matlab-runner") + self.assertEqual( + provenance["contributor"], + { + "public_display_name": "njustar2002", + "public_email": "njustar2002@vip.163.com", + }, + ) + self.assertEqual( + provenance["authorization"]["source_code_publication"], + "approved", + ) + self.assertEqual( + provenance["authorization"]["approval_basis"], + "repository-owner-confirmation", + ) + self.assertEqual( + provenance["authorization"]["target_license"], "MIT" + ) + self.assertEqual( + provenance["submission"]["sha256"], + "4e6b2f3c83abffd28148bcc36d4f96906d64be14b282fed9db2d32c1d89ad5f3", + ) + + def test_repository_metadata_lists_matlab_runner(self): + registry = (REPOSITORY_ROOT / "skills" / "registry.yaml").read_text( + encoding="utf-8" + ) + readme = (REPOSITORY_ROOT / "README.md").read_text(encoding="utf-8") + readme_zh = (REPOSITORY_ROOT / "README.zh-CN.md").read_text( + encoding="utf-8" + ) + contributors = (REPOSITORY_ROOT / "CONTRIBUTORS.md").read_text( + encoding="utf-8" + ) + for content in [registry, readme, readme_zh, contributors]: + self.assertIn("matlab-runner", content) + self.assertIn("njustar2002@vip.163.com", contributors) + + def test_public_tree_excludes_private_or_generated_artifacts(self): + forbidden_names = { + ".DS_Store", + "opencode.json", + "FINAL_SUBMISSION_AUDIT.md", + } + forbidden_suffixes = {".zip", ".tar", ".tgz", ".gz", ".7z", ".rar"} + found = [ + path.relative_to(PACKAGE_ROOT).as_posix() + for path in PACKAGE_ROOT.rglob("*") + if path.name in forbidden_names + or (path.is_file() and path.suffix.lower() in forbidden_suffixes) + ] + self.assertEqual(found, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_repository_shape.py b/tests/test_repository_shape.py index 7177f89..a92788c 100644 --- a/tests/test_repository_shape.py +++ b/tests/test_repository_shape.py @@ -3,14 +3,45 @@ import hashlib import py_compile import re +import tempfile import unittest from pathlib import Path +import yaml + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] PACKAGE_ROOT = REPOSITORY_ROOT / "skills" / "math-glossary" +def provenance_digests(skills_root: Path) -> set[str]: + digests = set() + for provenance_path in sorted(skills_root.glob("*/PROVENANCE.yaml")): + provenance = yaml.safe_load( + provenance_path.read_text(encoding="utf-8") + ) + digest = provenance.get("submission", {}).get("sha256") + if not isinstance(digest, str) or not re.fullmatch( + r"[0-9a-f]{64}", digest + ): + raise AssertionError( + f"Invalid submission SHA-256 in {provenance_path}" + ) + digests.add(digest) + return digests + + +def unregistered_archives( + archives: list[Path], expected_digests: set[str] +) -> list[str]: + return [ + archive.name + for archive in archives + if hashlib.sha256(archive.read_bytes()).hexdigest() + not in expected_digests + ] + + class RepositoryShapeTests(unittest.TestCase): def test_required_repository_files_exist(self): required = [ @@ -79,20 +110,28 @@ def test_private_archive_is_ignored_and_checksum_matches_when_present(self): ) self.assertIn(".intake/", gitignore) - provenance = (PACKAGE_ROOT / "PROVENANCE.yaml").read_text( - encoding="utf-8" - ) - expected = re.search(r"sha256: ([0-9a-f]{64})", provenance) - self.assertIsNotNone(expected) + expected_digests = provenance_digests(REPOSITORY_ROOT / "skills") archives = list( (REPOSITORY_ROOT / ".intake" / "original-submissions").glob("*.zip") ) if not archives: self.skipTest("Private intake archive is not present in this checkout") - self.assertEqual(len(archives), 1) - digest = hashlib.sha256(archives[0].read_bytes()).hexdigest() - self.assertEqual(digest, expected.group(1)) + self.assertEqual(unregistered_archives(archives, expected_digests), []) + + def test_multiple_private_archives_are_supported(self): + with tempfile.TemporaryDirectory() as temporary_directory: + archive_root = Path(temporary_directory) + archives = [archive_root / "one.zip", archive_root / "two.zip"] + archives[0].write_bytes(b"first private submission") + archives[1].write_bytes(b"second private submission") + expected_digests = { + hashlib.sha256(archive.read_bytes()).hexdigest() + for archive in archives + } + self.assertEqual( + unregistered_archives(archives, expected_digests), [] + ) def test_released_rights_gate_is_complete(self): registry = ( @@ -138,6 +177,7 @@ def test_third_party_boundary_is_documented(self): "Apache-2.0", "mdict-utils", "User-supplied dictionaries", + "do not share one server among multiple users", ]: with self.subTest(expected=expected): self.assertIn(expected, notice)