Compare commits

...

4 Commits

Author SHA1 Message Date
3bebd6468f Document form selection-summary readOnly folding contract (revealInForm)
All checks were successful
技能自动化发布 / release (push) Successful in 8s
2026-09-03 14:50:29 +08:00
e7fa3c79b5 Assert CLI version output matches SKILL_VERSION 2026-09-03 13:52:00 +08:00
0ce0c3e852 Enforce POLICY-RELEASE-001: align git tag with SKILL.md and SKILL_VERSION
All checks were successful
技能自动化发布 / release (push) Successful in 6s
2026-09-03 13:51:00 +08:00
201d4d1367 Release v1.0.103: forbid redundant market-doc H1
All checks were successful
技能自动化发布 / release (push) Successful in 6s
2026-09-03 10:32:41 +08:00
20 changed files with 278 additions and 40 deletions

View File

@@ -9,6 +9,22 @@
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
## 1.0.105
- 沉淀数据管理 form 与「已选择」摘要对齐契约:映射 `readOnly` 字段默认不占参数区(仍提交),与 confirm 信息架构一致
- 新增可选 `revealInForm` 逃生阀;明确禁止用 `visibleWhen` 藏映射主键
- 更新 ACTIONS.md / Schema / SKILL_ACTION_RUNTIME / CHECKLIST A4-8
## 1.0.104
- 新增发布契约 POLICY-RELEASE-001:`release.ps1` 打 tag 前强制校验 Next tag = `SKILL.md` version = `SKILL_VERSION`,避免市场一直显示「更新」
- 日常测试增加 `SKILL.md` 与 `SKILL_VERSION` 对齐;更新 DEVELOPMENT / POLICY_MATRIX / CHECKLIST A9-4
## 1.0.103
- 用户市场文档:说明/教程正文禁止与宿主页头、Tab 重复的文首一级标题;主分节统一用 `##`
- 更新 `TUTORIAL.md` / `README.md` 模板骨架、`TUTORIAL_SURFACES.md` §0.1、CHECKLIST A2-12 与契约测试
## 1.0.102
- 沉淀数据管理 Action「已选择」摘要契约:confirm/form 弹层由宿主默认展示勾选对象(#id + 可读标签);技能零改动即可用

View File

@@ -7,9 +7,8 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
> frontmatter 的 `description` 必须是用户能看懂的一句话能力描述;**不得**写成 CLI、schema、references、development、契约或「如何开始使用」类教程承诺。
> **禁止**写入:分步操作(1/2/3)、Agent 示例话术块、「入口一/入口二」专章、逐步排障。这些一律写在根目录 `TUTORIAL.md`。
> 正文只回答:是什么、能做什么、适合谁、准备什么、得到什么、注意事项。完整契约见 `development/TUTORIAL_SURFACES.md` §1。
> **复制后验收章节(标题可微调):**「能帮你做什么」「适合什么场景」「使用前需要准备什么」「怎么使用」「执行后你会得到什么」「注意事项」「常见问题」。
# 【技能名称】
> **复制后验收章节(标题可微调):**「能帮你做什么」「适合什么场景」「使用前需要准备什么」「怎么使用」「执行后你会得到什么」「注意事项」「常见问题」。
> **标题层级:** 宿主页头已有技能名——**禁止**文首再写 `# 技能名`;正文主分节用 `##`。
> 用一两句话说明这个技能帮助用户完成什么工作。
> 这里不要解释技术实现,要从用户的业务目标出发描述价值。

View File

@@ -1,7 +1,7 @@
---
name: 技能开发模板(通用业务版)
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
version: 1.0.102
version: 1.0.105
author: 深圳匠厂科技有限公司
metadata:
openclaw:

View File

@@ -4,9 +4,8 @@
> 与根 `README.md`(技能说明)分工:README 讲「是什么 / 能做什么 / 边界」;本文讲「从哪个入口怎么用完」。
> **完整契约:** `development/TUTORIAL_SURFACES.md`(三入口结构、说明≠教程、skill-detail 不可作主路径)。
> **复制后验收章节(标题可微调,语义不可缺):**「本技能做什么」「开始前」「入口一:数据管理」「入口二:对话任务」「入口三:定时任务」「主能力对照表」「常见卡住怎么办」。
> 若本技能 §0.1 未挂数据管理 / 对话 / 定时,对应入口节改为「不适用」并写一句理由(与 REQUIREMENTS 一致)。
# 【技能名称】· 使用教程
> 若本技能 §0.1 未挂数据管理 / 对话 / 定时,对应入口节改为「不适用」并写一句理由(与 REQUIREMENTS 一致)。
> **标题层级:** 宿主页头已有技能名、Tab 已有「使用教程」——**禁止**文首再写 `# 技能名 · 使用教程`;正文主分节用 `##`,步骤用 `###`。
本技能有两种**平行**的使用方式(按本技能入口定位填写;未挂的入口删掉或写不适用):

View File

@@ -6,7 +6,7 @@
- `examples/`:CLI 成功输出形状示例、Action 形状参考(含 `path-root-file-action.example.json`、`selection-label-column.action.example.json`、`render-html.action.example.json` 等;虚构路径与数据)。
- `schemas/`:轻量 JSON Schema(`skill-actions.schema.json` 为**新技能严格规范**)。
`skill-actions.schema.json` 使用 `additionalProperties: false`。正式支持 `bind.tables`(toolbar Action 必填)与可选 `bind.inputMapping` / `columnMapping` / **`labelColumn`**(选择摘要标签列);placements 含 `toolbar` / `row` / `batch` / `cron` / `agent` / `skill-detail`(对齐匠厂宿主)。数据管理 confirm/form 弹层默认展示「已选择」摘要(宿主能力,技能可不改)。可选 `hoverHint` 为数据管理工具栏悬停(省略不显示,不回退)。`concurrency`/`locks` 仍为预留。`placements` 与 `executionProfile` 正交——进任务中心只看 `async`,与入口无关。表级 Action 与 Agent 契约见 [`references/ACTIONS.md`](../references/ACTIONS.md);开发深度规范见 [`development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。
`skill-actions.schema.json` 使用 `additionalProperties: false`。正式支持 `bind.tables`(toolbar Action 必填)与可选 `bind.inputMapping` / `columnMapping` / **`labelColumn`**(选择摘要标签列);placements 含 `toolbar` / `row` / `batch` / `cron` / `agent` / `skill-detail`(对齐匠厂宿主)。数据管理 confirm/form 弹层默认展示「已选择」摘要;**form + 有摘要时映射 `readOnly` 默认不占参数区(仍提交)**,可选 **`revealInForm`** 强制显示。可选 `hoverHint` 为数据管理工具栏悬停(省略不显示,不回退)。`concurrency`/`locks` 仍为预留。`placements` 与 `executionProfile` 正交——进任务中心只看 `async`,与入口无关。表级 Action 与 Agent 契约见 [`references/ACTIONS.md`](../references/ACTIONS.md);开发深度规范见 [`development/SKILL_ACTION_RUNTIME.md`](../development/SKILL_ACTION_RUNTIME.md)。
- 用户市场四 Tab 见根目录 [`README.md`](../README.md) / [`TUTORIAL.md`](../TUTORIAL.md) / [`DEMO.md`](../DEMO.md) / [`CHANGELOG.md`](../CHANGELOG.md);写法契约见 [`development/TUTORIAL_SURFACES.md`](../development/TUTORIAL_SURFACES.md)
- Agent 调用/编排参考见 [`references/`](../references/)(含 [`ACTIONS.md`](../references/ACTIONS.md))

View File

@@ -110,7 +110,11 @@
"sensitive": { "type": "boolean" },
"readOnly": {
"type": "boolean",
"description": "宿主数据管理表单只读展示该字段(仍会随提交传入 entrypoint);用于行内/勾选预填的主键等"
"description": "只读。有「已选择」且字段来自 inputMapping(或约定 id 字段)时,宿主 form 默认不渲染该控件,预设值仍提交;无摘要时只读展示。勿用 visibleWhen 代替(visibleWhen 隐藏不提交)。"
},
"revealInForm": {
"type": "boolean",
"description": "可选逃生阀。true 时即使弹层有「已选择」摘要,也仍渲染该 readOnly 字段。不改变提交 payload。省略=映射只读字段在有摘要时默认折叠。"
},
"column": {
"type": "string",

View File

@@ -86,6 +86,7 @@ checklist_version: 1.0
- [ ] A2-9 **P0** 无 `cron` 时教程有「不需要定时」+ 理由;有 `cron` 时教程写清如何配置
- [ ] A2-10 **P1** 教程主能力对照表与 `actions.json` 的 `label` 一致
- [ ] A2-11 **P1** 若有数据管理主路径:准备/载入/导入类能力在对应表 **toolbar** 可见;教程**不以** skill-detail 为唯一主路径
- [ ] A2-12 **P0** 说明/教程正文**不以** `# 技能名` 或 `# 技能名 · 使用教程` 开篇(宿主页头/Tab 已有);主分节用 `##`,不要把原 `##` 升成 `#`(见 `TUTORIAL_SURFACES.md` §0.1)
---
@@ -109,7 +110,7 @@ checklist_version: 1.0
- [ ] A4-5 **P1** 已对外暴露的动作参数覆盖主路径关键输入(无“命令行能做、动作入口不能做”的关键缺口)
- [ ] A4-6 **P0** 若 Action 需引用同库另一张业务表(话术/平台/账号等):参数为 Lookup 下拉(内联 `lookup` 或绑列),**不是**手填内部编号;可选「自动」可留空提交
- [ ] A4-7 **P1** 若 Action 有互斥采集/操作模式(如「按关键词 / 按主页」):用 `inputSchema` 字段级 **`visibleWhen`** 联动显隐,勿只靠 `description` 灰字说明「仅某某时填写」(见 `references/ACTIONS.md`;勿与 Policy L3 行态 `visibleWhen` 混淆)
- [ ] A4-8 **P1** 行内/勾选打开的 `form`:有「已选择」时参数区不应再重复映射主键(`inputMapping` + `readOnly`);需要强制露出时用 `revealInForm`,**禁止**用 `visibleWhen` 藏主键(会不提交)
---
## 5. 账号与浏览器会话(网页 RPA 适用;其他形态标 N/A 或改测等价项)
@@ -186,7 +187,7 @@ checklist_version: 1.0
- [ ] A9-1 **P0** 仓库默认测试套件通过(执行技能约定的一键测试命令,如 `python tests/run_tests.py -v`)
- [ ] A9-2 **P0** 不以「仅模拟档通过」作为交活依据(有真实档要求时必须真实跑通或书面阶段性豁免)
- [ ] A9-3 **P0** **NEED_MANUAL** 入口定位表中定为「挂」的宿主入口(详情/Agent/工具栏/定时等)按声明抽测可通过;未在定位表中规划且未声明的入口可标 N/A(须引用定位表理由),**禁止**用「未声明」掩盖「从未做入口定位」
- [ ] A9-4 **P1** 对外版本一致:详情、version、更新日志、发布标签能对上
- [ ] A9-4 **P0** 对外版本一致(POLICY-RELEASE-001):`SKILL.md` version = `SKILL_VERSION` = 发布 git tag(无前缀);`release.ps1` 打 tag 前会硬校验,漏改不得发布;CHANGELOG 有对应小节
- [ ] A9-5 **P1** 开发者按平台规则能安装并测到该技能(若平台有开发者可见性要求)
---

View File

@@ -352,9 +352,9 @@ git remote -v
| `DEMO.md` | 演示 | `demo_md` + frontmatter `demo_video_url` |
| `CHANGELOG.md` | 更新日志 | 匹配当前 tag 版本的 `##` 小节 → `changelog` |
**`README.md`(说明 Tab)**:能力、场景、前置条件、结果形态、注意事项、概念类 FAQ。含 frontmatter `description`(**不要**写「如何开始使用」)。**禁止**分步操作、Agent 话术块、「入口一/二」专章、逐步排障——这些属教程。完整契约见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) §1。
**`README.md`(说明 Tab)**:能力、场景、前置条件、结果形态、注意事项、概念类 FAQ。含 frontmatter `description`(**不要**写「如何开始使用」)。**禁止**分步操作、Agent 话术块、「入口一/二」专章、逐步排障——这些属教程。**禁止**文首再写 `# 技能名`(页头已有);正文主分节用 `##`。完整契约见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) §0.1 / §1。
**`TUTORIAL.md`(教程 Tab)**:按 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) 写三入口结构(数据管理 ∥ 对话;定时有则教、无则 N/A)+ 主能力对照表 + 常见卡住。步骤必须与 `REQUIREMENTS` §0.1、`actions.json` 及宿主真实 UI 对齐;**禁止**把仅 `skill-detail` 的 Action 写成用户主路径。不要写 CLI / 目录 / 开发规范。
**`TUTORIAL.md`(教程 Tab)**:按 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) 写三入口结构(数据管理 ∥ 对话;定时有则教、无则 N/A)+ 主能力对照表 + 常见卡住。步骤必须与 `REQUIREMENTS` §0.1、`actions.json` 及宿主真实 UI 对齐;**禁止**把仅 `skill-detail` 的 Action 写成用户主路径。**禁止**文首 `# 技能名 · 使用教程`;正文最高用 `##`。不要写 CLI / 目录 / 开发规范。
**`DEMO.md`**:演示视频与补充说明。frontmatter 必须含 `demo_video_url`(无视频时用 `""`);正文可选。
@@ -720,9 +720,15 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
在执行 `release.ps1` 之前,必须先在本地确认 `python tests/run_tests.py -v` 通过。测试不通过不得发起正式发布。
**发布前必须**将 `SKILL.md` 的 `version:` 与 `scripts/util/constants.py` 的 `SKILL_VERSION` 调整为将打出的版本(例如下一 patch),再执行 `release.ps1`。`release.ps1` **不会**自动改这两个字段;漏改会造成 git tag 与技能版本号不一致。
**POLICY-RELEASE-001(硬门禁)**:发布前必须将 `SKILL.md` 的 `version:`、`scripts/util/constants.py` 的 `SKILL_VERSION`、以及 `CHANGELOG.md` 对应小节,调整为**即将打出的**版本(与 `.\release.ps1 -DryRun` 打印的 `Next tag` 去掉 `v` 前缀一致)。`release.ps1` **不会**自动改这些字段;若三者与 Next tag 不一致,脚本在跑测试/打 tag **之前**直接失败,避免市场「最新版本」(git tag) 与安装包「当前版本」(本地 `SKILL.md`) 长期错位、技能市场一直显示「更新」。
`release.ps1` 会在 **auto commit 之前**、推送 tag 之前,对**当前工作区**自动执行默认测试(`python tests/run_tests.py -v`);失败则中止 release,不会留下失败状态的自动提交。仅紧急排障可使用 `-SkipTests` 跳过(会打印黄色警告)。`-DryRun` 预览发布时**仍会实际运行**默认测试,便于在不打 tag 的情况下验证门禁。
建议流程:
1. `.\release.ps1 -DryRun` 查看 `Next tag`(例如 `v1.0.104`)
2. 将 `SKILL.md` / `SKILL_VERSION` / `CHANGELOG` 改为 `1.0.104`
3. 再执行正式 `.\release.ps1`(可用 `-AutoCommit`)
`release.ps1` 会在 **auto commit 之前**、推送 tag 之前,对**当前工作区**自动执行默认测试(`python tests/run_tests.py -v`);失败则中止 release,不会留下失败状态的自动提交。仅紧急排障可使用 `-SkipTests` 跳过(会打印黄色警告)。`-DryRun` 预览发布时**仍会实际运行**版本对齐门禁与默认测试,便于在不打 tag 的情况下验证。
新增或调整开发规范时,须同步 [`development/POLICY_MATRIX.md`](POLICY_MATRIX.md) 与 [`tests/test_development_policy_guard.py`](../tests/test_development_policy_guard.py)(或注明由既有测试覆盖)。

View File

@@ -24,6 +24,7 @@
| POLICY-RPA-006 | 浏览器 RPA 必须保留 account-manager 生命周期标准;所有样例账号选择路径都须返回非空 `profile_dir + lease_token`,指定数字账号 ID 不得退化为无租约 `account get` | development/ACCOUNT_MANAGER.md;development/ADAPTER.md §兄弟依赖;development/RPA.md §0.2 | hard | 检查标准文档关键标记;扫描真实/仿真 `account_client.py` 的指定 ID 带租约实现并禁止空租约赋值 | `tests/test_development_policy_guard.py::TestPolicyRpa006` |
| POLICY-PACKAGING-001 | `scripts/**/*.py` 单文件 < 1000 行 | development/DEVELOPMENT.md §3.4 发布打包约束;development/RUNTIME.md §发布打包约束 | hard | 行数统计 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_scripts_py_files_under_pyarmor_line_limit` |
| POLICY-PACKAGING-002 | 文本文件 UTF-8 without BOM,无 `U+FEFF` | development/DEVELOPMENT.md §3.4;development/RUNTIME.md §编码与输出 | hard | BOM / 解码检查 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_text_files_are_utf8_without_bom` |
| POLICY-RELEASE-001 | 发布 tag(去掉前缀)必须等于 `SKILL.md` 的 `version:` 且等于 `scripts/util/constants.py` 的 `SKILL_VERSION`;`release.ps1` 打 tag 前硬失败,不自动改写字段 | development/DEVELOPMENT.md §15 发布;宿主市场「最新版本」来自 tag、「当前版本」来自包内 `SKILL.md` | hard | `release.ps1` 门禁 + 日常版本字段对齐测试 | `release.ps1`(`Assert-SkillReleaseVersionAlignment`);`tests/test_development_policy_guard.py::TestPolicyRelease001`;`tests/test_skill_metadata.py::test_skill_version_alignment` |
| POLICY-DOCS-001 | 本矩阵存在且包含上述全部 `policy_id` | 本次规范化约定 | hard | 解析 `development/POLICY_MATRIX.md` | `tests/test_development_policy_guard.py::TestPolicyDocs001` |
| POLICY-LOGGING-001 | CLI 入口必须调用 `setup_skill_logging` | development/LOGGING.md;development/RUNTIME.md | hard | 扫描 `scripts/cli/app.py` 含 `setup_skill_logging` 与 logger info | `tests/test_development_policy_guard.py::TestPolicyLogging001` |
| POLICY-LOGGING-002 | service 主任务入口必须使用 `get_skill_logger` | development/LOGGING.md;development/DEVELOPMENT.md | hard | 扫描 `scripts/service/task_service.py` | `tests/test_development_policy_guard.py::TestPolicyLogging002` |
@@ -41,7 +42,7 @@
| POLICY-SKILL-ACTION-002 | 每个 Action 必须显式声明 `executionProfile`,且只能是 `sync` 或 `async` | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | schema required + manifest 扫描 | `tests/test_development_policy_guard.py::TestPolicySkillAction002`;`tests/test_actions_manifest.py` |
| POLICY-SKILL-ACTION-003 | toolbar Action 必须声明合法非空 `bind.tables`(snake_case、唯一) | references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | schema if/then + manifest 扫描 | `tests/test_development_policy_guard.py::TestPolicySkillAction003`;`tests/test_actions_manifest.py` |
| POLICY-SKILL-ACTION-004 | `placements` 与 `executionProfile` 正交;文档不得把数据管理入口与异步执行方式硬绑定;Schema 须接受 toolbar/row/batch/cron/agent/skill-detail × sync/async 全部组合 | development/SKILL_ACTION_RUNTIME.md;references/ACTIONS.md;assets/schemas/skill-actions.schema.json | hard | Schema 6×2 矩阵校验 + 文档禁止短语扫描 | `tests/test_actions_manifest.py::test_schema_allows_all_placement_execution_profile_combinations`;`tests/test_development_policy_guard.py::TestPolicySkillAction004` |
| POLICY-TUTORIAL-001 | 存在 `development/TUTORIAL_SURFACES.md`;`TUTORIAL.md` 须含数据管理/对话/定时/对照表骨架;`README.md` 禁止「告诉 Agent」专章与入口专章;业务技能教程与 `actions.json` placements 对齐(无 cron 须声明不需要定时) | development/TUTORIAL_SURFACES.md;development/CHECKLIST.md A2-7~A2-11 | hard | 文档存在性 + 章节/反模式扫描 + placements 对齐 | `tests/test_tutorial_surfaces.py`;`tests/test_docs_standards.py::test_tutorial_surfaces_contract_doc_and_checklist` |
| POLICY-TUTORIAL-001 | 存在 `development/TUTORIAL_SURFACES.md`;`TUTORIAL.md` 须含数据管理/对话/定时/对照表骨架;`README.md` 禁止「告诉 Agent」专章与入口专章;说明/教程正文禁止与页头重复的 `#` H1(主分节用 `##`);业务技能教程与 `actions.json` placements 对齐(无 cron 须声明不需要定时) | development/TUTORIAL_SURFACES.md;development/CHECKLIST.md A2-7~A2-12 | hard | 文档存在性 + 章节/反模式扫描 + placements 对齐 | `tests/test_tutorial_surfaces.py`;`tests/test_docs_standards.py::test_tutorial_surfaces_contract_doc_and_checklist` |
| POLICY-ARCH-CORE-001 | 同一业务能力的 Action、CLI、Agent、cron、数据管理入口复用同一业务内核(文档要求已声明) | development/DEVELOPMENT.md;development/SKILL_ACTION_RUNTIME.md | hard(文档标记) / soft(语义) | 文档关键表述存在性;真实复用靠 sample/review | `tests/test_development_policy_guard.py::TestPolicyArchCore001`;`tests/samples/test_action_core_contract.py.sample` |
| POLICY-DATA-SYNC-001 | 同步型业务须遵循幂等、事务、完整性与空结果保护(文档已声明) | references/SCHEMA.md;development/SKILL_ACTION_RUNTIME.md | hard(文档标记) / soft(语义) | 文档关键表述存在性;完整语义靠 sample/review | `tests/test_development_policy_guard.py::TestPolicyDataSync001`;`tests/samples/test_sync_contract.py.sample` |
@@ -68,6 +69,7 @@
| `SKILL.md` / `constants.SKILL_SLUG` 一致性 | development/DEVELOPMENT.md §16 | 已有 `tests/test_skill_metadata.py` |
| platform_kit_min_version >= 1.2.11 | development/RUNTIME.md | 已有 `tests/test_platform_import.py` |
| 宿主按 `bind.tables` 过滤 toolbar 按钮 | references/ACTIONS.md | 待宿主升级后完整生效;模板侧已强制显式绑定 |
| form +「已选择」时映射 `readOnly` 默认不占参数区(仍提交);`revealInForm` 逃生阀 | references/ACTIONS.md「选择摘要」 | soft(宿主 UX);文档与 schema 已声明;勿用 `visibleWhen` 代替 |
| 数据管理技能按钮中文名(`label`)对照 ACTIONS.md 建议表;打开类成功静默 | references/ACTIONS.md「Action 按钮中文名」 | 参考非白名单;行业词允许自创;勿做 label 关键字扫描 |
| Data Policy 未启用(L0)时宿主不得拉 Snapshot / 不得改按钮与行列行为 | references/POLICY.md | soft;宿主实现契约 |
| Data Policy 启用后 fail-closed(禁止回退 L0 全开);写路径 domain 必须再鉴权 | references/POLICY.md §6–§7 | soft;`POLICY-SKILL-AUTHZ-*` 硬门禁可后续加 |

View File

@@ -9,7 +9,7 @@
| 文档 | 职责 |
|------|------|
| 本文 | sync/async、任务中心、Agent 禁令、单内核编排、队列 pick、同步 vs 导出 |
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup、字段条件显隐 `visibleWhen`、本地路径 `file`/`directory` + 可选 **`pathRoot`**、**按钮中文名 `label` 参考** |
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup、字段条件显隐 `visibleWhen`、本地路径 `file`/`directory` + 可选 **`pathRoot`**、选择摘要「已选择」与映射 **`readOnly` / `revealInForm`**、**按钮中文名 `label` 参考** |
| [`LOGGING.md`](LOGGING.md) | `job_context` / `emit` / `finish` / Run Journal |
| [`DATA_PATHS.md`](DATA_PATHS.md) | 下载/导入等落盘路径 |
@@ -21,7 +21,7 @@
1. **宿主管调度,技能管业务** — 不自行实现任务中心 / Job 轮询 / 后台守护进程。
2. **单业务内核、多入口** — 数据管理、定时任务、Agent、技能详情与直接 CLI 复用同一 domain service;禁止按入口复制业务逻辑。
3. **五职责正交** — `placements` / `bind.tables` / `executionProfile` / `entrypoint` / domain service 各管一事(见 ACTIONS.md)。可选第六轴 **policy**(见 `references/POLICY.md`):未启用 = L0 与今日一致。可选 **`hostEffects` / `steps`**(见 ACTIONS.md):数据管理交互前后的 UI/级联编排;省略 = 不额外改 UI(匠厂宿主 ≥2.8.5)。可选 **`hoverHint`**(见 ACTIONS.md):数据管理工具栏悬停;省略 = 不显示,不回退说明或确认黄条。可选 **`visibleWhen`**(见 ACTIONS.md):Action 参数表单字段条件显隐;省略 = 字段始终平铺;**勿与** Policy L3 行态 `visibleWhen` 混淆。可选 **`pathRoot`**(见 ACTIONS.md):`file`/`directory` 原生选路对话框起始目录(相对技能数据根;省略默认 `{skill_data}/input`)。可选 **`bind.labelColumn`**(见 ACTIONS.md):选择摘要可读标签列;省略 = 宿主常见列名启发。可选 **`cellPresentation`**(见 `references/CELL_PRESENTATION.md`):单元格语义标色;省略 = P0。
3. **五职责正交** — `placements` / `bind.tables` / `executionProfile` / `entrypoint` / domain service 各管一事(见 ACTIONS.md)。可选第六轴 **policy**(见 `references/POLICY.md`):未启用 = L0 与今日一致。可选 **`hostEffects` / `steps`**(见 ACTIONS.md):数据管理交互前后的 UI/级联编排;省略 = 不额外改 UI(匠厂宿主 ≥2.8.5)。可选 **`hoverHint`**(见 ACTIONS.md):数据管理工具栏悬停;省略 = 不显示,不回退说明或确认黄条。可选 **`visibleWhen`**(见 ACTIONS.md):Action 参数表单字段条件显隐;省略 = 字段始终平铺;**勿与** Policy L3 行态 `visibleWhen` 混淆;**勿**用其藏映射主键。可选 **`pathRoot`**(见 ACTIONS.md):`file`/`directory` 原生选路对话框起始目录(相对技能数据根;省略默认 `{skill_data}/input`)。可选 **`bind.labelColumn`**(见 ACTIONS.md):选择摘要可读标签列;省略 = 宿主常见列名启发。可选 **`revealInForm`**(见 ACTIONS.md):有「已选择」时仍强制显示某 `readOnly` 字段;省略 = 映射只读默认不占 form 参数区(仍提交)。可选 **`cellPresentation`**(见 `references/CELL_PRESENTATION.md`):单元格语义标色;省略 = P0。
4. **长耗时建议 async** — 开浏览器、RPA、HTTP 大文件下载等建议 `executionProfile: "async"`,进**任务中心**;这是对**方法**的选择,**不**限制可以出现在哪些 placement / 入口。
5. **Agent:短 CLI / 长 Action** — 短查询可用共享 Python bash CLI;长任务 / RPA **禁止** `exec` + `process poll`,必须 `run_skill_action`。
6. **进度走 Journal** — `emit` / `video.add_step` → Run Journal;UI / Skill Run Card / 任务中心展示;**不靠 stdout 流**。

View File

@@ -28,6 +28,23 @@ README.md(能力与边界;不写步骤)
**铁律:** 说明就是说明,教程就是教程。**禁止**在 README 里写分步操作、Agent 话术块、排障「点哪里」;这些一律进 `TUTORIAL.md`。
### 0.1 标题层级(说明 / 教程共用)
宿主详情页**已经**展示技能名;「说明 / 使用教程」Tab(及数据管理右侧教程抽屉)**已经**标明文档类型。因此:
| 层级 | 用途 |
|------|------|
| `#` | **正文不要用**(会与页头/Tab 标题重复抢视觉) |
| `##` | 主分节(能帮你做什么、入口一:数据管理…) |
| `###` | 分节内步骤 |
**禁止:**
- `README.md` 文首 `# 【技能名】` / `# 审计疑点辅助` 这类与页头重复的 H1
- `TUTORIAL.md` 文首 `# 【技能名】· 使用教程` 这类与 Tab/抽屉标题重复的 H1
去掉重复 H1 后,**不要**把原来的 `##` 全部升成 `#`。
---
## 1. README.md(说明 Tab)
@@ -36,7 +53,7 @@ README.md(能力与边界;不写步骤)
| 问题 | 写法 |
|------|------|
| 这是什么 | 标题 + 一两句价值 |
| 这是什么 | 开篇一两句价值(**普通段落或引用**,不要再写技能名 H1) |
| 能做什么 | 3~6 条能力 bullet(**无步骤编号**) |
| 适合谁 / 场景 | 业务语言 |
| 使用前准备 | 安装、账号、权限等**前置条件**;不写「去哪张表点哪」 |
@@ -60,7 +77,7 @@ README.md(能力与边界;不写步骤)
### 1.4 建议章节(标题可微调)
```text
# 【技能中文名】
(开篇一两句价值;不要写 # 技能名)
## 这个技能能帮你做什么
## 适合什么场景
## 使用前需要准备什么
@@ -77,8 +94,7 @@ README.md(能力与边界;不写步骤)
复制业务技能后,**验收章节(标题可微调,语义不可缺)**:
```text
# 【技能中文名】· 使用教程
(开篇:数据管理与对话平行;定时有/无一句)
(开篇:数据管理与对话平行;定时有/无一句。不要写 # 技能名 · 使用教程)
## 本技能做什么 ← 业务数据流(非 CLI/SQL)
## 开始前

View File

@@ -110,10 +110,11 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
| **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`、`columnMapping`、**`labelColumn`**(行内/批量预填与选择摘要标签,见下节) |
| **policy** | 可选;`enabled` + `entrypoint`(推荐 `policy` CLI);见 POLICY.md。未启用 = L0 |
| **cellPresentation** | 可选;`{ "enabled": true }` 时宿主应加载单元格呈现;见 [`CELL_PRESENTATION.md`](CELL_PRESENTATION.md)。未启用 = P0 |
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly`;可选 `display_type` / `format`(含 `file` / `directory` 等);可选 **`pathRoot`**(file/directory 起始目录,相对技能数据根);可选 **`visibleWhen`**(字段条件显隐,见专节) |
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly`、可选 **`revealInForm`**;可选 `display_type` / `format`(含 `file` / `directory` 等);可选 **`pathRoot`**(file/directory 起始目录,相对技能数据根);可选 **`visibleWhen`**(字段条件显隐,见专节) |
| confirmation | `{ "message": "..." }` — **风险文案**(confirm/form 弹窗黄条;agent 元数据);**不是**「要不要弹窗」的开关 |
| **hoverHint** | 可选字符串。数据管理**工具栏悬停**文案;与 `description` / `confirmation.message` **独立**。省略则不显示悬停,不回退。短名已够懂时不要写。需宿主已支持该字段;当前实现为顶栏技能按钮,不含宿主内置删除/刷新/新增/导入/导出 |
| **visibleWhen** | 可选。写在 **property** 上(非 action 顶层):依赖同表单其它字段值决定是否展示;隐藏字段不提交、不校验 `required`。**勿与** [`POLICY.md`](POLICY.md) L3 行态 `visibleWhen` 混淆 |
| **visibleWhen** | 可选。写在 **property** 上(非 action 顶层):依赖同表单其它字段值决定是否展示;隐藏字段不提交、不校验 `required`。**勿与** [`POLICY.md`](POLICY.md) L3 行态 `visibleWhen` 混淆;也**勿**用它藏「已选择」已表达的映射主键(见「选择摘要」) |
| **revealInForm** | 可选。写在 **property** 上:有「已选择」时仍强制画出该 `readOnly` 字段;省略 = 映射只读字段默认不占参数区(仍提交) |
| **executionProfile** | `"sync"` \| `"async"`(**必须显式写出**,禁止依赖缺省) |
| **interaction** | `"auto"` \| `"direct"` \| `"confirm"` \| `"form"`(可选;省略≈`auto`) |
| **interactionByPlacement** | 可选;按 placement 覆盖为 `direct`/`confirm`/`form`(不可写 `auto`) |
@@ -138,19 +139,31 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
- `row` / `batch` **建议**同样声明 `bind.tables`,避免在无关表上露出按钮;未声明时宿主可能按兼容逻辑在更多表上展示。
- 表名须真实存在于技能 SQLite 与 `_jiangchang_tables`。
- 希望出现在多张表时,显式列出所有表;**不要**依赖「无 bind 则全表展示」——那是宿主对**旧技能**的兼容,不是新技能标准。
- **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段;可用 `inputSchema.properties.*.readOnly: true` 在表单中只读展示。
- **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段。映射字段建议标 `readOnly: true`:无「已选择」时只读展示;**有「已选择」时默认不占参数区**(仍提交,见下节)。不要用 `visibleWhen` 去「藏」映射主键。
- **`columnMapping`(可选)**:把 inputSchema 属性名映射到物理列名,供表单复用列展示名 / lookup。
- **`labelColumn`(可选)**:物理列名,供数据管理 Action 弹层「已选择」摘要展示 `#id + 标签`。**仅影响展示,不改变 CLI/提交 payload,也不决定业务是否跳过某些状态。** 省略时宿主用常见列名启发(如 `title` / `name` / `nickname` 等);再没有则只显示编号。列名不常见、或启发会选错列时再显式声明。
- `concurrency` / `locks` 仍未稳定,**不要**写入新技能 manifest。
### 数据管理:选择摘要(宿主默认能力)
当用户在数据管理**勾选行**后打开 `confirm` / `form` 弹层时,宿主会展示通用 **「已选择 · N 条」** 摘要(`#主键` + 可读标签;超过约 3 条可展开查看全部)。这是**宿主 UX**,对所有技能默认生效:
当用户在数据管理**勾选行**(或行内打开带行上下文的动作)后打开 `confirm` / `form` 弹层时,宿主会展示通用 **「已选择 · N 条」** 摘要(`#主键` + 可读标签;超过约 3 条可展开查看全部)。这是**宿主 UX**,对所有技能默认生效:
- **技能零改动即可受益**;不要在 `confirmation.message` 里手写「已选 id 列表」,也不要为了面板去拼业务状态跳过文案。
- 摘要与执行解耦:勾选了谁由 UI 说明;**实际处理哪些行、是否跳过某些状态,仍由技能 CLI / domain service 决定**。
- 需要稳定标签列时,在 `bind` 上声明可选 `labelColumn`(见上)。示例:[`assets/examples/selection-label-column.action.example.json`](../assets/examples/selection-label-column.action.example.json)。
**与 `confirm` / `form` 的信息架构对齐(映射只读字段):**
| 交互 | 参数区 | 映射主键(`inputMapping` + `readOnly`) |
|------|--------|----------------------------------------|
| `confirm` | 整区不画 | 不画;预设值仍提交 |
| `form`(有「已选择」) | 只画用户要填/要改的字段 | **默认不画**;预设值仍提交 |
| `form`(无「已选择」) | 正常画字段 | `readOnly` **只读展示**(如工具栏靠 lookup 手选作业) |
- 需要用户在有「已选择」时仍看见某只读字段:该 property 加 **`revealInForm: true`**(逃生阀;不改变提交)。
- **禁止**用 `visibleWhen` 藏映射主键:`visibleWhen` 隐藏的字段**不进提交**、不校验 `required`,会弄断 `--id` / `{{job_id}}`。
- 非映射的只读说明字段(未出现在 `inputMapping`)在有摘要时**仍会显示**;仅映射上下文字段被默认折叠。
## `replaces`(可选:接管默认新增 / 编辑 / 删除 / 导入)
有领域副作用的表(归一化字段、建目录、加密、级联清理)不要依赖宿主直写 SQLite。此时可为该表声明 `replaces`,**用户仍点宿主默认按钮**,由技能 CLI 完成写入。
@@ -340,8 +353,8 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
| 值 | 数据管理体验 | 典型场景 |
|----|--------------|----------|
| `direct` | **一点即跑**,无弹窗 | 打开目录、打开浏览器、无参只读、行内主键已映射 |
| `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`;有勾选时宿主展示「已选择」摘要) | 处理待发送、删除前确认、有副作用但无可编辑入参 |
| `form` | 完整参数表单(有勾选时同样展示「已选择」摘要) | 导入、查询条件、需用户改写的字段 |
| `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`;有勾选时宿主展示「已选择」摘要;映射主键仍提交) | 处理待发送、删除前确认、有副作用但无可编辑入参 |
| `form` | 完整参数表单(有勾选时同样展示「已选择」摘要;**映射 `readOnly` 默认不占参数区**,见「选择摘要」) | 导入、选文件、查询条件、需用户改写的字段 |
| `auto` / 省略 | 宿主推断:有**可编辑**参数 → `form`;仅有 `confirmation` → `confirm`;否则 → `direct` | 旧技能兼容;新技能建议显式写出 |
```json
@@ -969,7 +982,7 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
### 当前宿主稳定支持(摘要)
- 根:`object`;属性:`string` / `number` / `boolean`(`object`/`array` 仅作 JSON 多行编辑兼容,新技能慎用)
- `enum` / `default` / `required` / `sensitive` / `readOnly`
- `enum` / `default` / `required` / `sensitive` / `readOnly` / 可选 `revealInForm`
- `display_type` / `format`:`textarea` | `date` | `datetime_unix_seconds` | `url` | `email` | **`lookup`** | **`file`** | **`directory`**
- 可选 **`pathRoot`**(仅 `file` / `directory`):相对技能数据根的起始目录;省略默认 `{skill_data}/input`
- 字段内联 `lookup`(与 CRUD `options_json` lookup 对象同构;可选 `extra_column`、`multiple`)
@@ -977,6 +990,7 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
- 简单模板替换(`{{fieldName}}`)
- 可选空数字字段提交时由宿主省略,避免 `""` 触发 number 校验失败
- **字段条件显隐 `visibleWhen`**(可选):依赖同表单其它字段值决定是否展示;隐藏字段不进提交、不校验 `required`
- **选择摘要下的映射只读折叠**:`form` +「已选择」时,`inputMapping` 映射且 `readOnly` 的字段默认不渲染(仍提交);`revealInForm: true` 可强制显示;**勿**用 `visibleWhen` 代替
### 字段条件显隐(`visibleWhen`)
@@ -1094,7 +1108,7 @@ CRUD 侧宿主义务与禁止事项见 [`SCHEMA.md`](SCHEMA.md)「表单与展
- `display_type`/`format: url` / `email` 为薄层输入类型(校验从宽,勿当强校验网关)
- `required` 字段在提交前校验
- `sensitive` 字段不得出现在低信任入口
- `readOnly: true` 时数据管理表单只读展示该字段(仍随提交传入);适合行内/勾选预填的主键、编号列表
- `readOnly: true`:数据管理表单中标记为只读;**有「已选择」且该字段来自 `inputMapping`(或无 mapping 时的约定 id 字段)时,宿主默认不渲染该控件,预设值仍随提交传入**(与 `confirm` 无参数区对齐信息架构)。无「已选择」时仍只读展示。需要强制露出时加 `revealInForm: true`。**不要**用 `visibleWhen` 藏映射主键(会不提交)。
- `placements` 含 `batch` 的动作在数据管理表顶常驻,无勾选时禁用(与表删除一致)
- 需要确认的副作用操作使用 `confirmation.message`
- 短名看不懂或会覆盖数据时,用独立的 `hoverHint`;不要把确认黄条复用成悬停

View File

@@ -7,6 +7,10 @@
Encryption, ZIP packaging, and marketplace sync are done by CI
(.github/workflows/release_skill.yaml → reusable-release-skill.yaml@main).
POLICY-RELEASE-001: before tagging, requires SKILL.md version and
scripts/util/constants.py SKILL_VERSION to equal the next tag (without prefix).
release.ps1 does not auto-edit those fields.
Requires: git, PowerShell 5+
After fetch, runs default tests against the current working tree (before auto-commit):
@@ -162,6 +166,93 @@ function Get-NextTag {
return "$TagPrefix$($latest.Major).$($latest.Minor).$([int]$latest.Patch + 1)"
}
function Read-SkillMdVersion {
param([Parameter(Mandatory = $true)][string]$SkillRoot)
$path = Join-Path $SkillRoot "SKILL.md"
if (-not (Test-Path -LiteralPath $path)) {
return $null
}
$text = Get-Content -LiteralPath $path -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
if ([string]::IsNullOrWhiteSpace($text)) {
return $null
}
$m = [regex]::Match($text, '(?m)^version:\s*["'']?([0-9]+\.[0-9]+\.[0-9]+)["'']?\s*$')
if (-not $m.Success) {
return $null
}
return $m.Groups[1].Value.Trim()
}
function Read-SkillConstantsVersion {
param([Parameter(Mandatory = $true)][string]$SkillRoot)
$path = Join-Path $SkillRoot "scripts\util\constants.py"
if (-not (Test-Path -LiteralPath $path)) {
return $null
}
$text = Get-Content -LiteralPath $path -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
if ([string]::IsNullOrWhiteSpace($text)) {
return $null
}
$m = [regex]::Match($text, '(?m)^SKILL_VERSION\s*=\s*["'']([0-9]+\.[0-9]+\.[0-9]+)["'']')
if (-not $m.Success) {
return $null
}
return $m.Groups[1].Value.Trim()
}
# POLICY-RELEASE-001: marketplace latest (git tag) must equal package manifest versions.
function Assert-SkillReleaseVersionAlignment {
param(
[Parameter(Mandatory = $true)][string]$SkillRoot,
[Parameter(Mandatory = $true)][string]$NextTag,
[Parameter(Mandatory = $true)][string]$TagPrefix
)
$skillMdPath = Join-Path $SkillRoot "SKILL.md"
if (-not (Test-Path -LiteralPath $skillMdPath)) {
return
}
$expected = $NextTag
if ($TagPrefix -and $expected.StartsWith($TagPrefix)) {
$expected = $expected.Substring($TagPrefix.Length)
}
$expected = $expected.Trim()
if ($expected -notmatch '^\d+\.\d+\.\d+$') {
throw "Release check failed (POLICY-RELEASE-001): cannot derive semver from Next tag '$NextTag'."
}
$mdVersion = Read-SkillMdVersion -SkillRoot $SkillRoot
$constVersion = Read-SkillConstantsVersion -SkillRoot $SkillRoot
$problems = @()
if ([string]::IsNullOrWhiteSpace($mdVersion)) {
$problems += "SKILL.md missing top-level version: X.Y.Z"
}
elseif ($mdVersion -ne $expected) {
$problems += "SKILL.md version=$mdVersion (want $expected)"
}
if ([string]::IsNullOrWhiteSpace($constVersion)) {
$problems += "scripts/util/constants.py missing SKILL_VERSION = `"X.Y.Z`""
}
elseif ($constVersion -ne $expected) {
$problems += "SKILL_VERSION=$constVersion (want $expected)"
}
if ($problems.Count -gt 0) {
throw (
"Release aborted (POLICY-RELEASE-001): Next tag=$NextTag requires manifest version $expected, but " +
($problems -join "; ") +
". Bump SKILL.md version and scripts/util/constants.py SKILL_VERSION to $expected (and CHANGELOG), then re-run release.ps1. " +
"release.ps1 does not auto-edit these fields."
)
}
Write-Host "Version alignment OK: tag=$NextTag == SKILL.md=$mdVersion == SKILL_VERSION=$constVersion" -ForegroundColor DarkGray
}
function Assert-SkillReleasePackagingSources {
param([Parameter(Mandatory = $true)][string]$SkillRoot)
@@ -283,6 +374,15 @@ try {
Assert-SkillReleasePackagingSources -SkillRoot $skillRoot
}
$nextTag = Get-NextTag -TagPrefix $Prefix
Write-Host "Next tag: $nextTag" -ForegroundColor Green
Assert-SkillReleaseVersionAlignment -SkillRoot $skillRoot -NextTag $nextTag -TagPrefix $Prefix
$existing = @(Get-GitOutput -GitArgs @("tag", "--list", $nextTag))
if ($existing.Length -gt 0) {
throw "Tag already exists: $nextTag"
}
Invoke-DefaultTests -SkillRoot $skillRoot -Skip:$SkipTests
Ensure-CleanOrAutoCommit -DoAutoCommit:$AutoCommit -NeedClean:$RequireClean -IsDryRun:$DryRun -Msg $CommitMessage
@@ -304,14 +404,6 @@ try {
}
}
$nextTag = Get-NextTag -TagPrefix $Prefix
Write-Host "Next tag: $nextTag" -ForegroundColor Green
$existing = @(Get-GitOutput -GitArgs @("tag", "--list", $nextTag))
if ($existing.Length -gt 0) {
throw "Tag already exists: $nextTag"
}
if ($DryRun) {
Write-Host "[DryRun] Would run: git tag -a $nextTag -F <utf8-msg-file> ($Message)" -ForegroundColor Yellow
Write-Host "[DryRun] Would run: git push origin $nextTag" -ForegroundColor Yellow

View File

@@ -1,6 +1,6 @@
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
SKILL_SLUG = "your-skill-slug"
SKILL_VERSION = "1.0.102"
SKILL_VERSION = "1.0.105"
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
PLATFORM_KIT_MIN_VERSION = "1.2.11"

View File

@@ -633,6 +633,8 @@ class TestActionsManifest(unittest.TestCase):
"CELL_PRESENTATION.md",
"labelColumn",
"已选择",
"revealInForm",
"映射只读",
):
self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}")
self.assertNotIn(

View File

@@ -14,7 +14,7 @@ from jiangchang_skill_core import config
# scripts/ 已由 _support 注入 sys.path
from cli.app import main
from util.constants import SKILL_SLUG
from util.constants import SKILL_SLUG, SKILL_VERSION
class TestCliSmoke(unittest.TestCase):
@@ -57,6 +57,7 @@ class TestCliSmoke(unittest.TestCase):
self.assertIn("version", payload)
self.assertIn("skill", payload)
self.assertEqual(payload["skill"], SKILL_SLUG)
self.assertEqual(payload["version"], SKILL_VERSION)
def test_init_db_creates_database_and_is_idempotent(self) -> None:
with IsolatedDataRoot(user_id="_cli_init_db"):

View File

@@ -40,6 +40,7 @@ POLICY_IDS = (
"POLICY-RPA-006",
"POLICY-PACKAGING-001",
"POLICY-PACKAGING-002",
"POLICY-RELEASE-001",
"POLICY-DOCS-001",
"POLICY-LOGGING-001",
"POLICY-LOGGING-002",
@@ -1530,6 +1531,44 @@ class TestPolicyDataSync001(unittest.TestCase):
)
class TestPolicyRelease001(unittest.TestCase):
def test_release_ps1_enforces_version_alignment(self) -> None:
skill_root = get_skill_root()
path = os.path.join(skill_root, "release.ps1")
self.assertTrue(
os.path.isfile(path),
msg=_policy_msg("POLICY-RELEASE-001", "release.ps1", "file missing"),
)
text = _read_text(path)
self.assertIn(
"Assert-SkillReleaseVersionAlignment",
text,
msg=_policy_msg(
"POLICY-RELEASE-001",
"release.ps1",
"must define Assert-SkillReleaseVersionAlignment",
),
)
self.assertIn(
"POLICY-RELEASE-001",
text,
msg=_policy_msg(
"POLICY-RELEASE-001",
"release.ps1",
"must reference POLICY-RELEASE-001",
),
)
self.assertRegex(
text,
r"Assert-SkillReleaseVersionAlignment\s+-SkillRoot",
msg=_policy_msg(
"POLICY-RELEASE-001",
"release.ps1",
"must invoke Assert-SkillReleaseVersionAlignment before tagging",
),
)
class TestPolicyDocs001(unittest.TestCase):
def test_policy_matrix_exists_and_lists_policy_ids(self) -> None:
skill_root = get_skill_root()

View File

@@ -191,6 +191,7 @@ class TestDocsStandards(unittest.TestCase):
checklist = self._read("development/CHECKLIST.md")
self.assertIn("A2-7", checklist)
self.assertIn("A2-8", checklist)
self.assertIn("A2-12", checklist)
self.assertIn("TUTORIAL_SURFACES.md", checklist)
def test_naming_md_exists_and_covers_standards(self) -> None:
@@ -417,8 +418,11 @@ class TestDocsStandards(unittest.TestCase):
checklist = self._read("development/CHECKLIST.md")
self.assertIn("A4-7", checklist)
self.assertIn("visibleWhen", checklist)
self.assertIn("A4-8", checklist)
self.assertIn("revealInForm", checklist)
runtime = self._read("development/SKILL_ACTION_RUNTIME.md")
self.assertIn("visibleWhen", runtime)
self.assertIn("revealInForm", runtime)
def test_guarded_docs_do_not_reference_amazon_implementation(self) -> None:
forbidden = (

View File

@@ -50,6 +50,26 @@ class TestSkillMetadata(unittest.TestCase):
self.assertEqual(SKILL_SLUG, skill_dir)
self.assertEqual(slug_md, skill_dir)
def test_skill_version_alignment(self) -> None:
"""SKILL.md version 必须与 constants.SKILL_VERSION 一致(POLICY-RELEASE-001 日常侧)。"""
root = get_skill_root()
md_path = os.path.join(root, "SKILL.md")
with open(md_path, encoding="utf-8") as f:
md = f.read()
match = re.search(r"(?m)^version:\s*[\"']?([0-9]+\.[0-9]+\.[0-9]+)[\"']?\s*$", md)
self.assertIsNotNone(match, "SKILL.md must declare top-level semver version")
assert match is not None
md_version = match.group(1)
from util.constants import SKILL_VERSION
self.assertEqual(
md_version,
SKILL_VERSION,
"SKILL.md version 必须与 constants.SKILL_VERSION 一致(发布前再与 git tag 对齐)",
)
self.assertRegex(SKILL_VERSION, r"^\d+\.\d+\.\d+$")
if __name__ == "__main__":
unittest.main()

View File

@@ -84,9 +84,32 @@ class TestTutorialSurfacesContract(unittest.TestCase):
"定时",
"skill-detail",
"对照表",
"正文不要用",
"禁止",
):
self.assertIn(marker, text, msg=f"TUTORIAL_SURFACES.md missing {marker!r}")
def test_market_docs_forbid_redundant_page_h1(self) -> None:
"""宿主已有技能名与 Tab 标题:说明/教程正文禁止再用 # 一级标题。"""
def strip_fences(src: str) -> str:
return re.sub(r"```.*?```", "", src, flags=re.S)
for rel in ("README.md", "TUTORIAL.md"):
body = strip_fences(_read(rel))
# YAML frontmatter 里不应出现 ATX H1;正文亦禁止 #
self.assertIsNone(
re.search(r"(?m)^#\s+\S", body),
msg=(
f"{rel} must not use ATX H1 (# …); host already shows skill name / tab. "
"Use ## for sections (see TUTORIAL_SURFACES.md §0.1)."
),
)
self.assertIsNone(
re.search(r"(?m)^#\s+.·\s*使用教程\s*$", body),
msg=f"{rel} must not start sections with '# … · 使用教程'",
)
def test_development_md_points_to_tutorial_surfaces(self) -> None:
text = _read("development/DEVELOPMENT.md")
self.assertIn(