Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 295b2356ac | |||
| 7257d09813 | |||
| 9d33c5ea2c | |||
| b5d120469b | |||
| d35d04f779 | |||
| cbebd19ab3 | |||
| 05764cac15 |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -9,6 +9,48 @@
|
||||
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
|
||||
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
|
||||
|
||||
## 1.0.102
|
||||
|
||||
- 沉淀数据管理 Action「已选择」摘要契约:confirm/form 弹层由宿主默认展示勾选对象(#id + 可读标签);技能零改动即可用
|
||||
- 新增可选 `bind.labelColumn`(仅影响展示,不改提交 payload,不决定业务跳过逻辑)
|
||||
- 更新 `skill-actions.schema.json`、`ACTIONS.md`、`SKILL_ACTION_RUNTIME.md`;新增 `selection-label-column.action.example.json`
|
||||
|
||||
## 1.0.101
|
||||
|
||||
- 沉淀 Action 表单本地路径契约:`file` / `directory` 可选 **`pathRoot`**(相对技能数据根的起始目录;省略则宿主默认打开本技能 `input/`)
|
||||
- 更新 `skill-actions.schema.json`、`ACTIONS.md`、`DATA_PATHS.md`;新增 `assets/examples/path-root-file-action.example.json`
|
||||
- 与宿主导入面板默认 `input/` 划清边界;禁止技能写死用户 ID 或绝对盘符
|
||||
|
||||
## 1.0.100
|
||||
|
||||
- 沉淀用户市场文档契约 `development/TUTORIAL_SURFACES.md`:**说明 Tab ≠ 教程 Tab**;教程按数据管理 ∥ 对话、定时可选三入口结构
|
||||
- 重写根 `README.md` / `TUTORIAL.md` 模板骨架;CHECKLIST 增 A2-7~A2-11;`actions.json` 准备类优先 toolbar、skill-detail 不可作教程主路径
|
||||
- 新增 `tests/test_tutorial_surfaces.py`(POLICY-TUTORIAL-001);REQUIREMENTS §0.1 增教程章节列与用户文档对齐要求
|
||||
|
||||
## 1.0.99
|
||||
|
||||
- 沉淀数据管理**表注册门禁**契约:存在 `_jiangchang_tables` 时,宿主仅展示注册且 `visible=1` 的表;孤儿物理表不展示(匠厂宿主 ≥6.8.25)
|
||||
- 新增「表增删改与退役」:`drop_deprecated_user_table()` 标准迁移;禁止只删元数据留物理表;新技能禁止 `task_logs`
|
||||
- `LOGGING.md` 可观测性三分法;`DATA_PATHS.md` 卸载不删用户数据;Checklist A6-13 / A6-14;`tests/samples/test_table_deprecation_migration.py.sample`
|
||||
|
||||
## 1.0.98
|
||||
|
||||
- 沉淀数据管理**列筛选与全局搜索**契约:`searchable` 同时控制列筛选字段列表与顶栏全局搜索;存库 value / 展示 label 分工;筛选栏枚举/boolean/lookup 用下拉 Select(与表单开关/分段单选区分)
|
||||
- 全局搜索:options label 映射、boolean 别名、lookup 多选 token、`datetime_unix_seconds` 日期区间
|
||||
- `ACTIONS.md` 表单对照表补充「列筛选」脚注;`CHECKLIST` A6-11 / A6-12;`display_metadata_validator` 对未开 searchable 的枚举/boolean 列软 warning
|
||||
|
||||
## 1.0.97
|
||||
|
||||
- 沉淀数据管理 HTML 预览**布局契约**:`references/ACTIONS.md` 新增「宿主预览布局」(流体铺满 iframe、禁横向滚动、表格断行等)
|
||||
- 升级 `assets/html/example-report.html` 为通用布局基线;`test_html_page_service` 守护不退化
|
||||
- `DEVELOPMENT.md` / `CHECKLIST`(S0-12、A6-10)/ `TESTING.md` 交叉引用
|
||||
|
||||
## 1.0.96
|
||||
|
||||
- 在 `references/ACTIONS.md` 沉淀数据管理技能按钮中文名(`label`)参考表:同类通用动作用同一动词,不是白名单
|
||||
- 打开类改为「打开目录 / 打开浏览器」,一点即开、成功不弹「执行成功」;示例不再使用「打开文件位置」
|
||||
- 交活 Checklist A7-7;POLICY_MATRIX 软规则:不扫描 label 关键字
|
||||
|
||||
## 1.0.95
|
||||
|
||||
- 正式沉淀「业务行生命周期」通用契约(`references/WORK_ITEM_STATUS.md`):用可操作/进行中/成功·失败·跳过类终态等**角色**约束处理队列,不绑死某一技能的状态名
|
||||
|
||||
10
DEMO.md
10
DEMO.md
@@ -15,9 +15,13 @@ demo_video_url: ""
|
||||
|
||||
## 建议观看顺序
|
||||
|
||||
1. 如何发起一次典型任务
|
||||
2. 执行过程中会看到什么提示 / 进度
|
||||
3. 成功后的结果长什么样
|
||||
建议与 `TUTORIAL.md` 三入口结构对齐(有则拍、无则跳过):
|
||||
|
||||
1. 数据管理:准备数据 → 主流程按钮 → 结果表/报告
|
||||
2. 对话任务(若有):一句自然语言触发同一能力
|
||||
3. 定时任务(若有):到点自动跑;若本技能无定时,演示中可说明「不需要定时」
|
||||
|
||||
成功后的结果长什么样(表、任务中心、HTML 等)
|
||||
|
||||
## 没有视频时
|
||||
|
||||
|
||||
60
README.md
60
README.md
@@ -1,12 +1,13 @@
|
||||
---
|
||||
description: "用用户能理解的方式说明这个技能能做什么、适合什么场景,以及如何开始使用。"
|
||||
description: "用用户能理解的方式说明这个技能能做什么、适合什么场景,以及边界与结果形态。"
|
||||
---
|
||||
|
||||
> **模板说明(复制后删除本段)**
|
||||
> 本文是技能市场详情与普通用户说明,**不是** Agent 参考索引、CLI 契约或开发文档。
|
||||
> frontmatter 的 `description` 必须是用户能看懂的一句话能力描述;**不得**写成 CLI、schema、references、development、契约、数据结构、开发规范或 Agent 参考索引。
|
||||
> 正文只回答用户关心的问题,**不要**把 Playwright、DOM、Python、RPA、references、development、schema 等作为面向用户的主说明内容;若业务上必须出现技术词,须用用户能理解的方式解释,且不能喧宾夺主。
|
||||
> **复制后验收章节(标题可微调,建议保留):**「能帮你做什么」「适合什么场景」「使用前需要准备什么」「你可以这样告诉 Agent」「执行后你会得到什么」「注意事项」「常见问题」。
|
||||
> 本文是技能市场 **「说明」** Tab(`readme_md`),**不是**使用教程。
|
||||
> frontmatter 的 `description` 必须是用户能看懂的一句话能力描述;**不得**写成 CLI、schema、references、development、契约或「如何开始使用」类教程承诺。
|
||||
> **禁止**写入:分步操作(1/2/3)、Agent 示例话术块、「入口一/入口二」专章、逐步排障。这些一律写在根目录 `TUTORIAL.md`。
|
||||
> 正文只回答:是什么、能做什么、适合谁、准备什么、得到什么、注意事项。完整契约见 `development/TUTORIAL_SURFACES.md` §1。
|
||||
> **复制后验收章节(标题可微调):**「能帮你做什么」「适合什么场景」「使用前需要准备什么」「怎么使用」「执行后你会得到什么」「注意事项」「常见问题」。
|
||||
|
||||
# 【技能名称】
|
||||
|
||||
@@ -17,7 +18,7 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
|
||||
|
||||
## 这个技能能帮你做什么
|
||||
|
||||
用 3 到 6 条业务能力说明技能价值,例如:
|
||||
用 3 到 6 条业务能力说明技能价值(**不要**写成操作步骤),例如:
|
||||
|
||||
- 【自动整理/查询/生成/同步/核对/提交】某类业务数据
|
||||
- 帮你减少重复操作,降低人工录入或核对成本
|
||||
@@ -38,42 +39,33 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
|
||||
|
||||
## 使用前需要准备什么
|
||||
|
||||
说明用户在使用前要准备的东西,例如:
|
||||
说明**前置条件**(装什么、要不要账号、要不要外站登录),**不要**写「去哪张表点哪个按钮」:
|
||||
|
||||
- **网页自动化类:** 首次运行可由技能自动登记平台账号(浏览器会话由系统隔离管理)。也可在「账号管理」里指定登录标识。登记账号 ≠ 已登录目标站
|
||||
- **账号在哪里看:** 已登记账号可在「数据管理 / 账号管理」中查看;每个账号使用独立浏览器会话,批量或多账号任务不会共用登录状态
|
||||
- **是否要登录目标站:** 按本技能说明(有的平台必须登录,有的公开页可不登录);与「是否有账号记录」不是一回事
|
||||
- **网页自动化类:** 可能需要在账号管理中登记平台账号;登记账号 ≠ 已登录目标站(细节见使用教程)
|
||||
- 相关业务权限(查询、导出、提交等)已具备
|
||||
- 需要处理的文件、时间范围、订单号、客户名称、项目名称等信息
|
||||
- 如果涉及验证码或人工确认,请保证能及时在弹出的浏览器窗口中操作
|
||||
- 需要处理的文件、时间范围、单号等信息(若适用)
|
||||
- 若涉及验证码或人工确认,使用时请能及时在弹出窗口中操作
|
||||
|
||||
---
|
||||
|
||||
## 你可以这样告诉 Agent
|
||||
## 怎么使用
|
||||
|
||||
用自然语言示例展示用户可以怎么发起任务,例如:
|
||||
本技能支持哪些宿主入口,用**一两句话**概括即可,例如:
|
||||
|
||||
> “帮我查询今天的【业务对象】,整理成表格。”
|
||||
>
|
||||
> “帮我核对这些【数据/单据】是否有异常,并告诉我问题在哪里。”
|
||||
>
|
||||
> “帮我把【时间范围】内的【业务数据】导出并生成处理报告。”
|
||||
>
|
||||
> “帮我根据这个文件里的内容,逐条完成【业务动作】。”
|
||||
- 支持 **数据管理**(点表操作)与 **对话任务**(自然语言);是否提供定时任务按本技能说明。
|
||||
|
||||
> 注意:示例应是自然语言,不要写命令行命令。
|
||||
**逐步操作、示例话术与排障,请查看技能市场「使用教程」Tab。**
|
||||
(完整写法见 `development/TUTORIAL_SURFACES.md`。)
|
||||
|
||||
---
|
||||
|
||||
## 执行后你会得到什么
|
||||
|
||||
说明用户会看到什么结果,例如:
|
||||
说明用户会看到什么**结果形态**(不要教如何点出来),例如:
|
||||
|
||||
- 任务是否成功完成
|
||||
- 处理了多少条数据
|
||||
- 哪些内容成功、哪些失败
|
||||
- 失败原因和建议处理方式
|
||||
- 生成的文件、表格、报告或记录
|
||||
- 处理了多少条数据;哪些成功、哪些失败
|
||||
- 生成的文件、表格、报告或业务表记录
|
||||
|
||||
---
|
||||
|
||||
@@ -90,18 +82,16 @@ description: "用用户能理解的方式说明这个技能能做什么、适合
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 为什么任务没有查到数据?
|
||||
> FAQ 写**概念与边界**;需要「点哪里」的排障写到「使用教程」。
|
||||
|
||||
> 可能是时间范围、关键词、账号权限或系统筛选条件不正确。请先确认输入信息是否准确。
|
||||
### 这个技能适合我吗?
|
||||
|
||||
### 为什么任务执行到一半需要我操作?
|
||||
> 若你的场景与上文「适合什么场景」一致,可以安装后按「使用教程」完成第一次操作。
|
||||
|
||||
> 可能遇到了登录、验证码、权限确认、二次审批或系统异常。请根据 Agent 的提示完成必要操作。
|
||||
### 我不会操作,从哪里开始?
|
||||
|
||||
### 结果文件在哪里查看?
|
||||
|
||||
> 请根据 Agent 返回的结果说明查看生成文件、任务记录或处理报告。
|
||||
> 请打开技能市场 **「使用教程」** Tab,按「入口一:数据管理」或「入口二:对话任务」任选一种跟做。
|
||||
|
||||
### 已经登记的账号在哪里查看?
|
||||
|
||||
> 请到匠厂的「数据管理 / 账号管理」查看账号记录和状态。若任务仍提示账号异常,请把账号编号、任务时间和界面提示原文提供给支持人员;不要发送密码、Cookie 或验证码。
|
||||
> 请到匠厂的「数据管理 / 账号管理」查看账号记录和状态。不要发送密码、Cookie 或验证码。
|
||||
|
||||
11
SKILL.md
11
SKILL.md
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 技能开发模板(通用业务版)
|
||||
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
|
||||
version: 1.0.95
|
||||
version: 1.0.102
|
||||
author: 深圳匠厂科技有限公司
|
||||
metadata:
|
||||
openclaw:
|
||||
@@ -109,10 +109,11 @@ python {baseDir}/scripts/main.py init-db
|
||||
|
||||
**回答「怎么用 / 能做什么」时的要求:**
|
||||
|
||||
1. 「能做什么 / 场景 / FAQ」以 `README.md` 为主;「第一次怎么用完」以 `TUTORIAL.md` 为主。
|
||||
1. 「能做什么 / 场景 / 概念类 FAQ」以 `README.md`(说明 Tab)为主;「点哪里 / 说什么 / 排障」以 `TUTORIAL.md`(教程 Tab)为主。说明 Tab **不应**含分步操作;若 README 仍有旧式步骤,以教程为准并提示开发者按 `development/TUTORIAL_SURFACES.md` 修正。
|
||||
2. **禁止**在回答中粘贴 `python …/scripts/main.py …` 或其它命令行(除非用户明确索要命令,或自称技术人员/开发者)。
|
||||
3. **禁止**为答用法而去 `dir` / `ls` / 列技能目录,或默认打开 `references/`、`development/`;仅当用户明确问命令、参数、集成或排错时再读 `references/`。
|
||||
4. **不要**向普通用户暴露 Playwright、DOM、Python、RPA、库表名、`error_code` 等实现细节,除非用户明确询问技术实现。
|
||||
5. 用户问「怎么用」时:**优先读 `TUTORIAL.md`**;不要只根据 README 臆造按钮路径。
|
||||
|
||||
**文档读取边界:**
|
||||
|
||||
@@ -125,13 +126,13 @@ python {baseDir}/scripts/main.py init-db
|
||||
|
||||
| 文档 | 读者 | 用途 |
|
||||
|------|------|------|
|
||||
| 根目录 `README.md` | 普通用户 | 市场 **说明** Tab(`readme_md`) |
|
||||
| 根目录 `TUTORIAL.md` | 普通用户 | 市场 **教程** Tab(`tutorial_md`) |
|
||||
| 根目录 `README.md` | 普通用户 | 市场 **说明** Tab(`readme_md`):能力/场景/边界;**非教程** |
|
||||
| 根目录 `TUTORIAL.md` | 普通用户 | 市场 **教程** Tab(`tutorial_md`):分入口操作;结构见 `development/TUTORIAL_SURFACES.md` |
|
||||
| 根目录 `DEMO.md` | 普通用户 | 市场 **演示** Tab(`demo_video_url` + `demo_md`) |
|
||||
| 根目录 `CHANGELOG.md` | 普通用户 | 市场 **更新日志** Tab(按版本小节 → `changelog`) |
|
||||
| `SKILL.md`(本文) | LLM / OpenClaw 平台 | 技能入口、触发与运行契约摘要 |
|
||||
| `references/` | Agent 编排 / 调用 | CLI、Action、schema 契约(Agent 主读) |
|
||||
| `development/` | 开发者 / AI 编程代理 | 需求、开发教程、测试、深度技术规范;**产品入口**见 `development/PRODUCT_SURFACES.md` |
|
||||
| `development/` | 开发者 / AI 编程代理 | 需求、开发教程、测试、深度技术规范;**产品入口**见 `development/PRODUCT_SURFACES.md`;**用户文档契约**见 `development/TUTORIAL_SURFACES.md` |
|
||||
|
||||
## 目录约定
|
||||
|
||||
|
||||
144
TUTORIAL.md
144
TUTORIAL.md
@@ -1,12 +1,34 @@
|
||||
> **模板说明(复制后删除本段)**
|
||||
> 本文对应技能市场 **「使用教程」** Tab(发布时同步为 `tutorial_md`)。
|
||||
> 面向普通用户写**可跟着做的步骤**,不要写 CLI、目录结构、release、开发规范。
|
||||
> 与根 `README.md`(技能说明)分工:README 讲「是什么 / 能做什么」;本文讲「第一次怎么用完」。
|
||||
> **复制后验收章节(标题可微调):**「开始前」「第一步…」「第二步…」「常见卡住怎么办」。
|
||||
> 与根 `README.md`(技能说明)分工:README 讲「是什么 / 能做什么 / 边界」;本文讲「从哪个入口怎么用完」。
|
||||
> **完整契约:** `development/TUTORIAL_SURFACES.md`(三入口结构、说明≠教程、skill-detail 不可作主路径)。
|
||||
> **复制后验收章节(标题可微调,语义不可缺):**「本技能做什么」「开始前」「入口一:数据管理」「入口二:对话任务」「入口三:定时任务」「主能力对照表」「常见卡住怎么办」。
|
||||
> 若本技能 §0.1 未挂数据管理 / 对话 / 定时,对应入口节改为「不适用」并写一句理由(与 REQUIREMENTS 一致)。
|
||||
|
||||
# 【技能名称】使用教程
|
||||
# 【技能名称】· 使用教程
|
||||
|
||||
用最短路径带用户完成一次成功使用。步骤要具体,可用「打开匠厂 → …」这类操作语言。
|
||||
本技能有两种**平行**的使用方式(按本技能入口定位填写;未挂的入口删掉或写不适用):
|
||||
|
||||
- **数据管理**:在左侧表上点工具栏 / 行内按钮
|
||||
- **对话任务**:用自然语言让 Agent 完成同一能力
|
||||
|
||||
两种方式触发同一套业务能力,结果一致,选你习惯的即可。
|
||||
定时任务是否提供:见下方「入口三」(没有则写「本技能不需要定时」+ 理由)。
|
||||
|
||||
---
|
||||
|
||||
## 本技能做什么
|
||||
|
||||
用业务语言画清闭环(**不要**写库表英文名或命令行),例如:
|
||||
|
||||
```text
|
||||
【输入/准备】 → 【核心动作】 → 【结果可见处】 → 【可选报告/导出】
|
||||
```
|
||||
|
||||
- **输入**:……
|
||||
- **输出**:……
|
||||
- **说明**:边界(教学/正式、是否接外站等)
|
||||
|
||||
---
|
||||
|
||||
@@ -14,43 +36,105 @@
|
||||
|
||||
确认:
|
||||
|
||||
1. 已安装并登录匠厂客户端
|
||||
2. 已在技能市场安装本技能(或更新到最新版)
|
||||
3. 【网页自动化类技能】首次运行一般会自动登记本平台账号;也可在「账号管理」中指定登录标识。浏览器会话由系统隔离管理——**不要**用本机日常浏览器用户目录去跑真实站
|
||||
4. 【按平台】若本技能说明「需要先登录目标站」,请在弹出的有头浏览器中按提示登录或扫码;「登记账号」不等于「已登录目标站」。若说明「可不登录也能用」,仍须有可用的账号会话目录
|
||||
5. 【网页自动化类技能】已登记账号可在「数据管理 / 账号管理」查看;每个账号都有独立浏览器会话
|
||||
6. 【如需】需要的文件 / 时间范围 / 单号等已准备好
|
||||
1. 已安装并登录匠厂客户端
|
||||
2. 已在技能市场安装本技能(建议更新到最新版)
|
||||
3. 【网页自动化类】账号登记 / 目标站登录要求(按本技能;登记 ≠ 已登录)
|
||||
4. 【如需】文件 / 时间范围 / 单号等已准备好
|
||||
5. 【如需】首次须先完成「载入样例 / 导入 / 同步」等准备动作(见入口一或入口二)
|
||||
|
||||
---
|
||||
|
||||
## 第一步:打开技能并说清目标
|
||||
## 入口一:数据管理
|
||||
|
||||
在匠厂里新建任务或打开对话,用自然语言说明要做什么,例如:
|
||||
> 若本技能无业务表、无 toolbar/row/batch:整节改为「不适用」+ 理由。
|
||||
|
||||
> “帮我【完成某件业务事】,范围是【时间 / 对象 / 条件】。”
|
||||
适合:对照数据、喜欢点界面、机房/运营用户。
|
||||
|
||||
建议一次只交代一件事;范围说清楚,减少来回确认。
|
||||
### 左侧各表是干什么的
|
||||
|
||||
| 表名(中文) | 作用 |
|
||||
|--------------|------|
|
||||
| 【表 A】 | …… |
|
||||
| 【表 B】 | …… |
|
||||
|
||||
### 首次准备(若需要)
|
||||
|
||||
1. 进入 **数据管理** → **【主表】**
|
||||
2. 点击表顶 **「【准备类按钮 label】」**(若为空表可点,写明是否须选中行)
|
||||
3. 成功后应看到:……
|
||||
|
||||
> **注意:** 准备/载入/导入类按钮须挂在数据管理 **toolbar**(用户看得见)。不要把「技能详情页点按钮」写成主路径——详情页常只显示四 Tab,不一定渲染 Action 按钮。
|
||||
|
||||
### 主流程
|
||||
|
||||
1. 进入 **【主表】**
|
||||
2. 【是否选中行】
|
||||
3. 点击 **「【主能力 label】」**
|
||||
4. 到 **【结果表 / 任务中心 / 报告】** 核对结果
|
||||
|
||||
### 行级 / 批量(若有)
|
||||
|
||||
| 你想做什么 | 操作 |
|
||||
|------------|------|
|
||||
| …… | **【表】** 行内 → **「【label】」** |
|
||||
| …… | 勾选多行 → **「【label】」** |
|
||||
|
||||
---
|
||||
|
||||
## 第二步:按提示补充信息
|
||||
## 入口二:对话任务
|
||||
|
||||
如果 Agent 问你缺什么(账号、文件、筛选条件),按提示补充即可。
|
||||
> 若本技能不挂 `agent`:整节改为「不适用」+ 理由。
|
||||
|
||||
- 有文件时:按界面提示选择或拖入
|
||||
- 有账号时:确认已在对应平台登录,且具备操作权限
|
||||
适合:临时提问、要解释、不想切换表。
|
||||
|
||||
### 什么时候用对话
|
||||
|
||||
- ……
|
||||
- 与入口一效果相同;习惯看表时优先用数据管理
|
||||
|
||||
### 示例话术
|
||||
|
||||
| 你想做什么 | 可以这样说 |
|
||||
|------------|------------|
|
||||
| …… | 「……」 |
|
||||
| …… | 「……」 |
|
||||
|
||||
### Agent 可能追问什么
|
||||
|
||||
- 缺参数时:……
|
||||
- 失败时:可改用数据管理点按钮
|
||||
|
||||
---
|
||||
|
||||
## 第三步:等待结果并核对
|
||||
## 入口三:定时任务
|
||||
|
||||
任务结束后,请核对:
|
||||
> **有 `cron`:** 写如何新建定时、选哪个 Action、默认参数、预期结果。
|
||||
> **无 `cron`:** 用下面「不需要」模板,删掉「如何配置」小节。
|
||||
|
||||
- 是否成功完成
|
||||
- 处理了多少条 / 哪些失败
|
||||
- 生成的文件、表格或记录是否在预期位置
|
||||
### 本技能不需要定时时(示例文案,按需保留/改写)
|
||||
|
||||
若失败,先看失败原因说明,再按下一节排查。
|
||||
本技能**未提供定时任务入口**,原因:【一次性 / 须人指定对象 / 须当场确认 / ……】。
|
||||
请使用 **数据管理** 或 **对话任务**。
|
||||
|
||||
### 本技能需要定时时(示例结构)
|
||||
|
||||
1. 打开侧栏 **定时任务** → 新建
|
||||
2. 选择本技能 → Action **「【label】」**
|
||||
3. 填写参数 → 保存;可「立即运行」验证
|
||||
4. 到点后在【结果表 / 任务中心】查看
|
||||
|
||||
---
|
||||
|
||||
## 主能力对照表
|
||||
|
||||
同一能力,两种主入口(按本技能实际填写;只挂一端的注明):
|
||||
|
||||
| 想做什么 | 数据管理 | 对话任务 |
|
||||
|----------|----------|----------|
|
||||
| …… | **【表】** → **「【label】」** | 「……」 |
|
||||
| …… | …… | …… |
|
||||
|
||||
按钮名必须与 `assets/actions.json` 的 `label` 一致。
|
||||
|
||||
---
|
||||
|
||||
@@ -58,10 +142,10 @@
|
||||
|
||||
| 现象 | 你可以先试 |
|
||||
|------|------------|
|
||||
| 提示未登录 / 无权限 | 确认匠厂已登录;业务账号是否在线、权限是否足够 |
|
||||
| 提示账号不存在 / 被占用 | 到「数据管理 / 账号管理」确认账号记录和状态,稍后再试;不要重复创建账号 |
|
||||
| 提示缺少必要信息 | 把时间范围、单号、文件等说完整后再试一次 |
|
||||
| 一直没有结果 | 到任务中心查看进度或失败原因;必要时重新发起 |
|
||||
| 结果不对 | 用更明确的条件再说一次,或缩小范围重试 |
|
||||
| 主表是空的 | 先做「首次准备」或对话载入/导入 |
|
||||
| 按钮点不了 | 是否须先选中行;是否装错版本 |
|
||||
| 一直没有结果 | 看任务中心 / 运行日志;async 任务看进度 |
|
||||
| 【网页自动化】未登录 / 无权限 | 确认账号在线与目标站登录状态 |
|
||||
| 结果不对 | 缩小范围重试,或改用数据管理核对表数据 |
|
||||
|
||||
仍无法解决时,把「你说了什么 + 界面提示原文」发给支持人员,便于排查。
|
||||
仍无法解决时,把「你用的入口 + 操作步骤 + 界面提示原文」发给支持人员。
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# assets
|
||||
|
||||
- `actions.json`:Skill Action manifest 最小示例(按入口定位表决定是否保留/扩展;见 `development/PRODUCT_SURFACES.md`)。每个 action **必须**显式声明 `executionProfile`(`sync` \| `async`)。默认仅 `health` / `version` / `config-path`,不放 toolbar,故无 `bind` 示例。
|
||||
- `html/example-report.html`:HTML 报告页最小壳(`{{TITLE}}` / `{{GENERATED_AT}}` / `{{BODY}}`);渲染后须写入数据目录 `html/`,见 `references/ACTIONS.md`「HTML 页面报告」。
|
||||
- `examples/`:CLI 成功输出形状示例、以及 `render-html.action.example.json`(虚构路径与数据)。
|
||||
- **业务技能注意:** 若主路径在数据管理,准备/载入/导入类 Action 应挂 `toolbar` + `bind.tables`(用户看得见),**不要**只挂 `skill-detail` 却在教程里写成「技能详情点按钮」。见 `development/TUTORIAL_SURFACES.md` §5、`references/ACTIONS.md`「用户可见性」。
|
||||
- `html/example-report.html`:HTML 报告页最小壳(`{{TITLE}}` / `{{GENERATED_AT}}` / `{{BODY}}`);含数据管理 iframe 预览布局基线(流体铺满、禁横向滚动);渲染后须写入数据目录 `html/`,见 `references/ACTIONS.md`「HTML 页面报告」。
|
||||
- `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`;placements 含 `toolbar` / `row` / `batch` / `cron` / `agent` / `skill-detail`(对齐匠厂宿主)。可选 `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 弹层默认展示「已选择」摘要(宿主能力,技能可不改)。可选 `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)
|
||||
- 用户市场四 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))
|
||||
- 开发规范见 [`development/`](../development/)
|
||||
|
||||
36
assets/examples/path-root-file-action.example.json
Normal file
36
assets/examples/path-root-file-action.example.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"_comment": "形状参考:Action 表单 file/directory 用 pathRoot 指定原生对话框起始目录(相对技能数据根)。省略则宿主默认 {skill_data}/input。完整契约见 references/ACTIONS.md「本地路径选择与 pathRoot」。勿与宿主导入面板(replaces:import)混用本示例。",
|
||||
"schemaVersion": 1,
|
||||
"skill": "your-skill-slug",
|
||||
"actions": [
|
||||
{
|
||||
"id": "import-pack",
|
||||
"label": "导入数据包",
|
||||
"description": "从本技能输入目录子文件夹选择 JSON 并写入业务表。",
|
||||
"placements": ["toolbar", "agent"],
|
||||
"executionProfile": "sync",
|
||||
"interaction": "form",
|
||||
"bind": { "tables": ["business_items"] },
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["file"],
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"title": "数据包文件",
|
||||
"description": "请选择 input/数据包/ 下的 JSON。",
|
||||
"format": "file",
|
||||
"display_type": "file",
|
||||
"pathRoot": "input/数据包"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "cli",
|
||||
"command": "import-pack",
|
||||
"args": ["--file", "{{file}}"]
|
||||
},
|
||||
"steps": [{ "when": "afterSuccess", "do": "reloadTable" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
38
assets/examples/selection-label-column.action.example.json
Normal file
38
assets/examples/selection-label-column.action.example.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"id": "process-selected",
|
||||
"label": "处理勾选",
|
||||
"description": "对勾选行执行业务处理。示例:声明 labelColumn,让确认弹层「已选择」摘要显示可读名称。",
|
||||
"placements": ["toolbar", "batch"],
|
||||
"executionProfile": "async",
|
||||
"interaction": "confirm",
|
||||
"confirmation": {
|
||||
"message": "将按技能业务规则处理勾选记录(具体是否跳过某些状态由 CLI 决定)。"
|
||||
},
|
||||
"bind": {
|
||||
"tables": ["business_items"],
|
||||
"inputMapping": {
|
||||
"ids": "$selection.ids"
|
||||
},
|
||||
"labelColumn": "display_name"
|
||||
},
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["ids"],
|
||||
"properties": {
|
||||
"ids": {
|
||||
"type": "string",
|
||||
"title": "编号列表",
|
||||
"description": "逗号分隔主键;宿主会预填勾选行",
|
||||
"readOnly": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "cli",
|
||||
"command": "process",
|
||||
"args": ["--ids", "{{ids}}"]
|
||||
},
|
||||
"hostEffects": {
|
||||
"onSuccess": ["reloadTable"]
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,31 @@
|
||||
--muted: #64748b;
|
||||
--card: #ffffff;
|
||||
--line: #e2e8f0;
|
||||
--page-pad-x: clamp(14px, 2.5vw, 24px);
|
||||
--page-pad-y: clamp(12px, 2vw, 20px);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { overflow-y: auto; }
|
||||
body {
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
line-height: 1.5;
|
||||
}
|
||||
header {
|
||||
padding: 20px 24px 8px;
|
||||
padding: var(--page-pad-y) var(--page-pad-x) 8px;
|
||||
background: var(--card);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
header h1 {
|
||||
margin: 0 0 6px;
|
||||
@@ -34,7 +48,23 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
main {
|
||||
padding: 8px 24px 28px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: var(--page-pad-y) var(--page-pad-x) 28px;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.section h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
color: var(--muted);
|
||||
}
|
||||
.meta {
|
||||
margin: 0 0 10px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
@@ -44,6 +74,35 @@
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
th {
|
||||
background: #f8fafc;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
tr:last-child td { border-bottom: none; }
|
||||
/*
|
||||
宿主数据管理在 iframe 内预览本页:应横向铺满可用宽度,仅纵向滚动。
|
||||
复杂看板可在 {{BODY}} 内使用 .section / table;布局底线见 references/ACTIONS.md「宿主预览布局」。
|
||||
*/
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -121,13 +121,18 @@
|
||||
"display_type": {
|
||||
"type": "string",
|
||||
"enum": ["textarea", "date", "datetime_unix_seconds", "url", "email", "lookup", "directory", "file"],
|
||||
"description": "可选。表单控件提示;与 format 二选一即可。lookup=关联下拉(须配 lookup 对象或绑到已有 lookup 列)。file/directory=本地路径选择。须与 CRUD display_type 语义对齐(见 ACTIONS.md / SCHEMA.md)。"
|
||||
"description": "可选。表单控件提示;与 format 二选一即可。lookup=关联下拉(须配 lookup 对象或绑到已有 lookup 列)。file/directory=本地路径选择(可选 pathRoot 指定起始目录,见 ACTIONS.md)。须与 CRUD display_type 语义对齐。"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["textarea", "date", "datetime_unix_seconds", "url", "email", "lookup", "directory", "file"],
|
||||
"description": "可选。display_type 别名,语义相同。"
|
||||
},
|
||||
"pathRoot": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "可选。仅对 display_type/format 为 file 或 directory 有意义:原生选文件/目录对话框的起始文件夹,相对当前技能数据根(如 input/库存数据)。省略则宿主默认打开 {skill_data}/input。禁止绝对路径、盘符与 ..。Agent/CLI 忽略。见 references/ACTIONS.md「本地路径选择与 pathRoot」。"
|
||||
},
|
||||
"lookup": {
|
||||
"$ref": "#/$defs/lookupConfig",
|
||||
"description": "可选。内联同库表关联 Lookup(与列 options_json lookup 对象同构)。用于当前表没有对应列时;推荐同时设 display_type=lookup。见 ACTIONS.md「跨表引用:Lookup」。"
|
||||
@@ -296,9 +301,15 @@
|
||||
"pattern": "^[a-z][a-z0-9_]*$"
|
||||
},
|
||||
"description": "可选。将 inputSchema 属性名映射到业务表物理列名,供 Action 弹窗与 replaces create/update 抽屉复用列 displayName / lookup options。"
|
||||
},
|
||||
"labelColumn": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z][a-z0-9_]*$",
|
||||
"description": "可选。数据管理 Action 确认/表单弹层「已选择」摘要用的可读标签列(#id + 标签)。仅影响展示,不改变提交 payload。省略时宿主按常见列名启发(title/name 等),再不行只显示编号。"
|
||||
}
|
||||
},
|
||||
"description": "表级绑定;toolbar 必填。可选 inputMapping / columnMapping。concurrency/locks 勿写入。"
|
||||
"description": "表级绑定;toolbar 必填。可选 inputMapping / columnMapping / labelColumn。concurrency/locks 勿写入。"
|
||||
},
|
||||
"hostEffectId": {
|
||||
"type": "string",
|
||||
|
||||
@@ -60,6 +60,7 @@ checklist_version: 1.0
|
||||
- [ ] S0-9 **入口定位表**(见 `PRODUCT_SURFACES.md` / `REQUIREMENTS` §0.1):已填写 / 未填写
|
||||
- [ ] S0-10 本技能宣称的主用户路径涉及:对话 / 数据管理(执行或仅展示) / 定时(可多选;与定位表一致)
|
||||
- [ ] S0-11 **业务行生命周期**(见 `references/WORK_ITEM_STATUS.md` / `REQUIREMENTS` §0.3):有处理队列且已声明状态→角色映射 / 不适用
|
||||
- [ ] S0-12 **HTML 页面报告**(见 `references/ACTIONS.md`「HTML 页面报告」):有侧栏可预览的 `html/*.html` + `render-html` / 不适用
|
||||
|
||||
---
|
||||
|
||||
@@ -76,10 +77,15 @@ checklist_version: 1.0
|
||||
|
||||
- [ ] A2-1 **P0** 市场「说明」能让非开发者理解:做什么、给谁用、开始前准备什么
|
||||
- [ ] A2-2 **P0** 「说明/教程」不以开发目录、内部实现细节作为用户主路径
|
||||
- [ ] A2-3 **P1** 「教程」可按真实顺序跟做(准备 → 步骤 → 失败怎么办)
|
||||
- [ ] A2-3 **P1** 「教程」可按真实顺序跟做(准备 → 分入口步骤 → 失败怎么办);结构见 `TUTORIAL_SURFACES.md`
|
||||
- [ ] A2-4 **P1** 「演示」结构完整(有演示信息位;无视频则标明暂无,不假装有)
|
||||
- [ ] A2-5 **P0** 「更新日志」能对应到当前交付版本
|
||||
- [ ] A2-6 **P0** 平台/Agent 侧说明中,常见用户意图的触发方式与真实能力一致(短操作 vs 长任务不混淆)
|
||||
- [ ] A2-7 **P0** 「说明」Tab(`README.md`)**不含**分步操作(1/2/3)、Agent 话术块、「入口一/二」专章;操作指引指向「使用教程」
|
||||
- [ ] A2-8 **P0** 「教程」Tab 覆盖 `REQUIREMENTS` §0.1 / `actions.json` 中所有「挂」的入口(数据管理 / 对话;结构见 `TUTORIAL_SURFACES.md`)
|
||||
- [ ] A2-9 **P0** 无 `cron` 时教程有「不需要定时」+ 理由;有 `cron` 时教程写清如何配置
|
||||
- [ ] A2-10 **P1** 教程主能力对照表与 `actions.json` 的 `label` 一致
|
||||
- [ ] A2-11 **P1** 若有数据管理主路径:准备/载入/导入类能力在对应表 **toolbar** 可见;教程**不以** skill-detail 为唯一主路径
|
||||
|
||||
---
|
||||
|
||||
@@ -134,6 +140,11 @@ checklist_version: 1.0
|
||||
- [ ] A6-7 **P0** 有「处理业务行」类 Action 时(见 `WORK_ITEM_STATUS.md`):默认只处理**可操作**角色状态;成功/失败/跳过/进行中再点处理应拒绝或跳过且不产生重复副作用;重做须人工改回可操作。无处理队列 → **N/A**
|
||||
- [ ] A6-8 **P0** 同上适用时:开始处理后业务表进入**进行中**;成功回写成功类终态;失败回写失败类终态且**失败原因**可读(任务成功但业务行仍停在可操作/进行中 → **FAIL**)
|
||||
- [ ] A6-9 **P1** 同上适用时:`REQUIREMENTS` §0.3(或等价)已填写本技能状态→角色映射;处理类 Action 确认文案写清仅可操作态
|
||||
- [ ] A6-10 **P1** 有 HTML 侧栏预览时(S0-12):预览区横向铺满、无横向滚动条;重新生成后内容与页头时间更新;布局符合 `references/ACTIONS.md`「宿主预览布局」。无 HTML 报告 → **N/A**
|
||||
- [ ] A6-11 **P1** 业务**状态 / 启用类**列若用户需要筛表或顶栏搜中文:`searchable: 1` 且 `options_json`(或 `boolean` + `options_json`)的 **value 与存库一致**;仅 `searchable: 0` 则宿主不提供列筛与全局搜索。见 `references/SCHEMA.md`「列筛选与全局搜索」
|
||||
- [ ] A6-12 **P2** `display_type: boolean` 的展示列推荐同时配置 `options_json` 中文 label(如 停用/启用),避免仅靠宿主默认「是/否」;无 boolean 列 → **N/A**
|
||||
- [ ] A6-13 **P0** 新技能**不得**创建 `task_logs`;过程/进度/业务结果走运行日志、任务中心、业务表(见 `LOGGING.md`、`SCHEMA.md`)
|
||||
- [ ] A6-14 **P0** 废弃用户可见表时:`drop_deprecated_user_table` 物理删表 + 清元数据;**禁止**只删元数据留物理表;有迁移时须提供旧库回归测试(见 `SCHEMA.md`、`tests/samples/test_table_deprecation_migration.py.sample`)
|
||||
|
||||
---
|
||||
|
||||
@@ -145,6 +156,7 @@ checklist_version: 1.0
|
||||
- [ ] A7-4 **P1** 关键步骤有据可查(能对应到开始/关键节点/结束;RPA 宜有多个 `stage=`)
|
||||
- [ ] A7-5 **P1** 若宣称支持录屏或截图存证:打开开关后关键步骤或失败能留下可查看产物;宣称支持却完全无产物 → **FAIL**
|
||||
- [ ] A7-6 **P2** 用户可见文案偏业务语言,而非大段开发黑话
|
||||
- [ ] A7-7 **P2** 数据管理技能按钮(toolbar / row / batch)中文名对照 `references/ACTIONS.md`「Action 按钮中文名」;不与宿主「查看 / 编辑 / 删除」抢名;打开类成功不弹成功条。行业专有按钮名可自创
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ D:\jiangchang-data\12500\download-video-baidu-haokan\
|
||||
|
||||
宿主约定(与本模板对齐):
|
||||
|
||||
- 工具栏「导入」**UI 一律**打开宿主「导入数据」面板(默认从 `input/` 选文件);「导出」默认写到 `output/`
|
||||
- 工具栏「导入」**UI 一律**打开宿主「导入数据」面板(默认从本技能 `input/` 选文件);「导出」默认写到 `output/`
|
||||
- **Action 表单**里 `display_type`/`format` 为 `file` 或 `directory` 时:宿主原生对话框默认打开**当前技能** `{skill_data}/input`;若字段声明可选 **`pathRoot`**(相对技能数据根,如 `input/库存数据`),则打开该子目录。禁止在技能里写用户 ID 或绝对盘符。契约见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「本地路径选择与 pathRoot」
|
||||
- 若表声明了 `replaces: import`,确认导入后由技能 CLI 写库(见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「`replaces`」);仅有 `replaces: create` 时宿主可按行调 create(兼容)
|
||||
- 数据管理扫描 `{skill_data}/html/*.html`(一层、非递归);`<title>` → 侧栏显示名;「重新生成」调用 Action id **`render-html`**(见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「HTML 页面报告」)
|
||||
|
||||
@@ -176,6 +177,10 @@ DOWNLOAD_OUTPUT_DIR=./outputs/videos
|
||||
- `tests/test_runtime_paths.py` 覆盖 `resolve_data_path` / `resolve_input_path`。
|
||||
- `POLICY-DATA-PATH-001`:禁止 `.env.example` 中 `./` 路径;禁止业务代码 `abspath(config.get(...))`。
|
||||
|
||||
### 卸载技能与数据保留
|
||||
|
||||
宿主**卸载技能只删除安装目录**(如 `.openclaw/skills/{slug}`),**不会**删除 `{JIANGCHANG_DATA_ROOT}/{user_id}/{skill_slug}/` 下的 SQLite、输入输出与日志。重装会复用同一用户库;schema 迁移须在 `init_db()` 中幂等完成(见 [`../references/SCHEMA.md`](../references/SCHEMA.md)「表增删改与退役」)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
@@ -352,9 +352,9 @@ git remote -v
|
||||
| `DEMO.md` | 演示 | `demo_md` + frontmatter `demo_video_url` |
|
||||
| `CHANGELOG.md` | 更新日志 | 匹配当前 tag 版本的 `##` 小节 → `changelog` |
|
||||
|
||||
**`README.md`**:能力与场景说明。含 frontmatter `description`;写能做什么、场景、准备、如何告诉 Agent、结果、注意事项、FAQ。不要写开发教程、目录规范、release、requirements。
|
||||
**`README.md`(说明 Tab)**:能力、场景、前置条件、结果形态、注意事项、概念类 FAQ。含 frontmatter `description`(**不要**写「如何开始使用」)。**禁止**分步操作、Agent 话术块、「入口一/二」专章、逐步排障——这些属教程。完整契约见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) §1。
|
||||
|
||||
**`TUTORIAL.md`**:第一次怎么用完的逐步操作。写「开始前 → 步骤 → 卡住怎么办」;不要写 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`(无视频时用 `""`);正文可选。
|
||||
|
||||
@@ -590,7 +590,7 @@ cmd_stats() # 只读 sync:JSON,无 job_context
|
||||
- 批量数量用 **`pickCount` / `--pick N` 参数**(任意正整数),不写死条数。
|
||||
- **顺序执行、不并发**同一浏览器 Profile / account lease。
|
||||
- **同步数据**与**宿主表导出**职责分离:同步不写 CSV/Excel;表导出交给数据管理。
|
||||
- **HTML 页面报告**(可选):需要侧栏预览整页时,写入 `html/` + Action `render-html`;契约见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「HTML 页面报告」、[`DATA_PATHS.md`](DATA_PATHS.md);薄写入 helper 见 `scripts/service/html_page_service.py`。
|
||||
- **HTML 页面报告**(可选):需要侧栏预览整页时,写入 `html/` + Action `render-html`;契约见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「HTML 页面报告」(含**宿主预览布局**底线)、[`DATA_PATHS.md`](DATA_PATHS.md);薄写入 helper 见 `scripts/service/html_page_service.py`。建议从 `assets/html/example-report.html` 复制壳;`{{BODY}}` 内数据须转义;样式升级后提醒用户重新生成。
|
||||
- 详情见 [`SKILL_ACTION_RUNTIME.md`](SKILL_ACTION_RUNTIME.md);契约样例见 `tests/samples/test_sync_contract.py.sample`、`tests/samples/test_action_core_contract.py.sample`。
|
||||
|
||||
### 队列 + pick(可选)
|
||||
@@ -837,7 +837,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- 长任务(async)是否有可见进度,能否取消或等待说明
|
||||
- 网页 RPA 是否有头运行;需登录/验证码时是否停下等人,而不是静默失败
|
||||
- 数据管理:表名/字段中文、按钮文案是否清楚;结果是否写回用户看得见的地方
|
||||
- 市场说明 / 教程是否与真实行为一致
|
||||
- 市场说明 / 教程是否与真实行为一致;**说明 Tab 不含分步操作**,教程覆盖已挂入口(见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md))
|
||||
|
||||
| 宿主入口(侧栏 / 界面) | 技能侧如何挂上 | 建议验收什么 |
|
||||
|-------------------------|----------------|--------------|
|
||||
|
||||
@@ -122,9 +122,15 @@ RPA 等待用 `interruptible_sleep`,**不要**裸 `asyncio.sleep`(否则暂
|
||||
|
||||
Journal 会写入 `type: lifecycle` 事件(`paused` / `resumed` / `stopping`),供宿主任务中心显示状态。
|
||||
|
||||
### 2.3 业务表(可选,不是默认排障通道)
|
||||
### 2.3 可观测性三分法(禁止 task_logs)
|
||||
|
||||
模板**默认不建**任务审计表。业务结果写入各技能自己的业务表;过程看 `jiangchang.log`,进度/终态看任务中心。
|
||||
| 信息类型 | 去哪里 | 技能怎么做 |
|
||||
|----------|--------|------------|
|
||||
| **过程**(步骤、异常栈、排查) | `jiangchang.log` / 宿主「运行日志」 | `log.info` / `log.exception`,关键节点见 §3 |
|
||||
| **进度 / 终态**(任务中心) | Run Journal:`emit` / `finish` | `job_context` + `finish(status=...)` |
|
||||
| **业务结果** | 业务表字段(status、产物路径、统计等) | repository 回写;见 [`WORK_ITEM_STATUS.md`](../references/WORK_ITEM_STATUS.md) |
|
||||
|
||||
模板**默认不建** `task_logs` 等通用任务审计表。**不要**用业务表冒充任务进度,也不要用任务表代替业务状态机。
|
||||
|
||||
### 2.4 RPA video step / artifacts(用户可见动作与存证)
|
||||
|
||||
|
||||
@@ -41,6 +41,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-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` |
|
||||
|
||||
@@ -67,6 +68,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 | 待宿主升级后完整生效;模板侧已强制显式绑定 |
|
||||
| 数据管理技能按钮中文名(`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-*` 硬门禁可后续加 |
|
||||
| `actions.mode` 仅 allowlist/open;`hostCapabilities` 省略=不限制;scope `valueFrom` 仅 `actor.userId` | references/POLICY.md §0.1 | soft;Schema 已约束 |
|
||||
|
||||
@@ -164,9 +164,25 @@
|
||||
|
||||
---
|
||||
|
||||
## 7. 相关文档
|
||||
## 7. 用户教程如何投影本文
|
||||
|
||||
入口定位表填完后,**必须**落到用户市场文档(见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md)):
|
||||
|
||||
| 定位结果 | `TUTORIAL.md` | `README.md` |
|
||||
|----------|---------------|-------------|
|
||||
| 挂数据管理 | 「入口一」专章 + 表/按钮步骤 | 仅一句「支持数据管理」 |
|
||||
| 挂对话 | 「入口二」专章 + 话术 | 仅一句「支持对话」;**禁止**话术块 |
|
||||
| 挂定时 | 「入口三」教怎么建 | 可选一句边界 |
|
||||
| 不挂定时 | 「入口三」写不需要 + 理由 | 可选一句「不提供定时」 |
|
||||
|
||||
准备/载入类 Action:优先 `toolbar` + `bind.tables`,避免教程写「技能详情点按钮」而宿主详情页不渲染。
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关文档
|
||||
|
||||
- 需求里填定位表:[`REQUIREMENTS.md`](REQUIREMENTS.md)
|
||||
- **用户说明 / 教程怎么写:** [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md)
|
||||
- 交活怎么验:[`CHECKLIST.md`](CHECKLIST.md)
|
||||
- 开发步骤中的 Action 挂载:[`DEVELOPMENT.md`](DEVELOPMENT.md) §11.5、§15
|
||||
- Agent 侧字段契约:[`../references/ACTIONS.md`](../references/ACTIONS.md)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|------|--------|------|
|
||||
| 1 | **拿到仓库**:① 项目经理在 [自建 Gitea](https://git.jc2009.com/)(`git.jc2009.com`,非 Gitea 官网)开仓并授权后 `git clone`;或 ② 本地已有 `skill-template` 时用 scaffold / 复制出新目录(遵守 Git 红线) | [`DEVELOPMENT.md`](DEVELOPMENT.md) §1、§4 |
|
||||
| 2 | **先读宿主产品入口**:对话 / 数据管理 / 定时任务平级;何时走哪个;为本技能填入口定位表 | [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md) |
|
||||
| 2b | **用户市场文档**:说明 Tab ≠ 教程 Tab;教程按三入口写;与 §0.1 / actions 对齐 | [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) |
|
||||
| 3 | **先读 AI + RPA 总纲**;界面技能先完成元素拾取/element map,再按浏览器、桌面或手机读分端手册 | [`AI_RPA.md`](AI_RPA.md)、[`RPA_ELEMENT_LOCATING.md`](RPA_ELEMENT_LOCATING.md) |
|
||||
| 4 | 按 [`NAMING.md`](NAMING.md) 确认 slug;**先填** [`REQUIREMENTS.md`](REQUIREMENTS.md)(含入口定位表),再写业务代码 | [`REQUIREMENTS.md`](REQUIREMENTS.md)、[`NAMING.md`](NAMING.md) |
|
||||
| 5 | 替换标识与占位,按四象限 / examples 实现 `scripts/service/` | [`DEVELOPMENT.md`](DEVELOPMENT.md) §4~§14 |
|
||||
@@ -27,6 +28,7 @@
|
||||
开始定制 skill 前,建议按以下顺序阅读:
|
||||
|
||||
1. [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md) — **宿主产品入口**:对话 / 数据管理 / 定时任务平级;何时走哪个;入口定位表
|
||||
1b. [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md) — **用户说明 / 教程契约**:README≠TUTORIAL;三入口结构;skill-detail 不可作主路径
|
||||
2. [`AI_RPA.md`](AI_RPA.md) — **AI Agent + RPA 总纲**:职责边界、合规红线、风控、操作节奏与完整闭环
|
||||
3. [`ACCOUNT_MANAGER.md`](ACCOUNT_MANAGER.md) — **浏览器账号标准**:一账号一 Profile、租约、多账号、数据查看、验收与排障
|
||||
4. [`SHARED_REPOS.md`](SHARED_REPOS.md) — **公共仓库目录**(模板 / 账号管理 / platform-kit 等,可扩展)
|
||||
|
||||
@@ -33,19 +33,26 @@
|
||||
|
||||
## 0.1 宿主入口定位(必填)
|
||||
|
||||
先读 [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md)。对话任务 / 数据管理 / 定时任务在宿主上**平级**;挂哪些由本技能业务与 UX 定,模板不规定「必须三入口都挂满」。
|
||||
先读 [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md)。对话任务 / 数据管理 / 定时任务在宿主上**平级**;挂哪些由本技能业务与 UX 定,模板不规定「必须三入口都挂满」。用户文档如何写见 [`TUTORIAL_SURFACES.md`](TUTORIAL_SURFACES.md)。
|
||||
|
||||
对每个主能力填一行(可增行;「不挂」必须写理由):
|
||||
|
||||
| 主能力(Action id / CLI) | 对话 | 数据管理 | 定时 | 不挂理由(若不挂) |
|
||||
|---------------------------|------|----------|------|--------------------|
|
||||
| (例)`<action-or-cli>` | 挂 / 不挂 | 不挂 / toolbar / row / batch / replaces / 仅展示表 | 挂 / 不挂 | |
|
||||
| 主能力(Action id / CLI) | 对话 | 数据管理 | 定时 | 教程章节 | 不挂理由(若不挂) |
|
||||
|---------------------------|------|----------|------|----------|--------------------|
|
||||
| (例)`<action-or-cli>` | 挂 / 不挂 | 不挂 / toolbar / row / batch / replaces / 仅展示表 | 挂 / 不挂 | 入口一 / 入口二 / 入口三 / N/A | |
|
||||
|
||||
说明:
|
||||
|
||||
- 数据管理列可写组合(如 `toolbar + row`)
|
||||
- 「仅展示表」= 用户要看过程/结果,但不在表上触发该能力
|
||||
- 定位为「挂」的入口须在 `assets/actions.json`(及必要表元数据)中落实,并与对话路径共用同一 domain service
|
||||
- **教程章节**:该能力在 `TUTORIAL.md` 中落在哪一节;整技能无定时时,入口三仍须写「不需要 + 理由」
|
||||
|
||||
### 0.1.1 用户市场文档(与入口表一致)
|
||||
|
||||
- `README.md`(说明 Tab):只写能力 / 场景 / 边界;**禁止**分步操作与 Agent 话术(见 `TUTORIAL_SURFACES.md` §1)
|
||||
- `TUTORIAL.md`(教程 Tab):覆盖 §0.1 中所有「挂」的入口;准备/载入类优先数据管理 toolbar,**不要**把 skill-detail 当主路径
|
||||
- 定位为「不挂」定时:教程须有 N/A 声明,与本表理由一致
|
||||
|
||||
## 0.2 AI + RPA 协作与授权边界(涉及外部系统时必填)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
| 文档 | 职责 |
|
||||
|------|------|
|
||||
| 本文 | sync/async、任务中心、Agent 禁令、单内核编排、队列 pick、同步 vs 导出 |
|
||||
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup、字段条件显隐 `visibleWhen` |
|
||||
| [`../references/ACTIONS.md`](../references/ACTIONS.md) | `actions.json` 字段、placements、`bind.tables`、inputSchema、跨表 Lookup、字段条件显隐 `visibleWhen`、本地路径 `file`/`directory` + 可选 **`pathRoot`**、**按钮中文名 `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` 混淆。可选 **`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):选择摘要可读标签列;省略 = 宿主常见列名启发。可选 **`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 流**。
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
- **发布打包守护**:[`tests/test_release_packaging_constraints.py`](../tests/test_release_packaging_constraints.py) ——`scripts/**/*.py` 单文件 < 1000 行、文本文件 UTF-8 without BOM。
|
||||
- **开发规范守护(hard policy)**:[`tests/test_development_policy_guard.py`](../tests/test_development_policy_guard.py) ——规则索引见 [`development/POLICY_MATRIX.md`](POLICY_MATRIX.md),只执行可机器检测的 hard 规则。
|
||||
- **slug 语义**:[`tests/test_slug_naming.py`](../tests/test_slug_naming.py) ——verb-noun-platform 命名规范校验。
|
||||
- **HTML 报告壳**:[`tests/test_html_page_service.py`](../tests/test_html_page_service.py) 守护 `assets/html/example-report.html` 的数据管理 iframe 预览布局基线;业务技能若有自定义 `assets/html/*.html`,建议在自有仓库加同类静态断言(见 `references/ACTIONS.md`「宿主预览布局」)。
|
||||
|
||||
**原则:`tests/test_*.py` 不允许隐形访问外网、不允许拉起真实浏览器、不允许读写开发者机器的真实数据根**。如果需要仿真服务器,也应仅在 integration(并由档位变量放行)。
|
||||
|
||||
|
||||
206
development/TUTORIAL_SURFACES.md
Normal file
206
development/TUTORIAL_SURFACES.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# 用户市场文档契约(说明 Tab / 教程 Tab)
|
||||
|
||||
本文给**技能开发者**与 **AI 编程助手**读:规定根目录 `README.md`(技能说明)与 `TUTORIAL.md`(使用教程)**写什么、不写什么**,以及如何与宿主三入口、入口定位表、`actions.json` 对齐。
|
||||
|
||||
**单一真相链:**
|
||||
|
||||
```text
|
||||
REQUIREMENTS.md §0.1 入口定位表
|
||||
↓
|
||||
assets/actions.json(placements / label / bind)
|
||||
↓
|
||||
TUTORIAL.md(用户可读、分入口操作)
|
||||
README.md(能力与边界;不写步骤)
|
||||
```
|
||||
|
||||
产品入口「挂什么」见 [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md)。本文只管**用户市场文档怎么投影**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 与四 Tab 的关系
|
||||
|
||||
| 文件 | 市场 Tab | 本文角色 |
|
||||
|------|----------|----------|
|
||||
| `README.md` | **说明** | §1:是什么 / 给谁 / 边界 |
|
||||
| `TUTORIAL.md` | **教程** | §2~§6:怎么用(分入口) |
|
||||
| `DEMO.md` | 演示 | 观看顺序可对齐三入口,不另造标准 |
|
||||
| `CHANGELOG.md` | 更新日志 | 与文档契约无关 |
|
||||
|
||||
**铁律:** 说明就是说明,教程就是教程。**禁止**在 README 里写分步操作、Agent 话术块、排障「点哪里」;这些一律进 `TUTORIAL.md`。
|
||||
|
||||
---
|
||||
|
||||
## 1. README.md(说明 Tab)
|
||||
|
||||
### 1.1 必须回答
|
||||
|
||||
| 问题 | 写法 |
|
||||
|------|------|
|
||||
| 这是什么 | 标题 + 一两句价值 |
|
||||
| 能做什么 | 3~6 条能力 bullet(**无步骤编号**) |
|
||||
| 适合谁 / 场景 | 业务语言 |
|
||||
| 使用前准备 | 安装、账号、权限等**前置条件**;不写「去哪张表点哪」 |
|
||||
| 执行后得到什么 | 结果形态(表、报告、文件),**不教怎么生成** |
|
||||
| 注意事项 | 边界、风险、人工复核 |
|
||||
| 怎么使用(可选,短) | **一两句**:支持哪些入口 + **「步骤见使用教程 Tab」** |
|
||||
|
||||
### 1.2 禁止写入 README
|
||||
|
||||
- `1.` `2.` `3.` 操作步骤(如「选品任务 → 点某某按钮」)
|
||||
- 「你可以这样告诉 Agent」整节或大块 `>` 示例话术
|
||||
- 「入口一 / 入口二」专章结构
|
||||
- 逐步排障(「先点 A 再点 B」)
|
||||
- frontmatter `description` 不要写「以及如何开始使用」类教程承诺
|
||||
|
||||
### 1.3 FAQ 边界
|
||||
|
||||
- README FAQ:**概念类**(和真实系统区别、从哪开始学、是否支持定时)→ 可指向「使用教程」Tab
|
||||
- **操作类**排障 → 只写在 `TUTORIAL.md`「常见卡住怎么办」
|
||||
|
||||
### 1.4 建议章节(标题可微调)
|
||||
|
||||
```text
|
||||
# 【技能中文名】
|
||||
## 这个技能能帮你做什么
|
||||
## 适合什么场景
|
||||
## 使用前需要准备什么
|
||||
## 怎么使用 ← 仅短摘要 + 指向教程
|
||||
## 执行后你会得到什么
|
||||
## 注意事项
|
||||
## 常见问题 ← 概念类
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. TUTORIAL.md(教程 Tab)必选结构
|
||||
|
||||
复制业务技能后,**验收章节(标题可微调,语义不可缺)**:
|
||||
|
||||
```text
|
||||
# 【技能中文名】· 使用教程
|
||||
(开篇:数据管理与对话平行;定时有/无一句)
|
||||
|
||||
## 本技能做什么 ← 业务数据流(非 CLI/SQL)
|
||||
## 开始前
|
||||
## 入口一:数据管理 ← §0.1 有 toolbar/row/batch/replaces 则必填
|
||||
## 入口二:对话任务 ← §0.1 有 agent 则必填
|
||||
## 入口三:定时任务 ← 有 cron:怎么用;无 cron:不需要 + 理由
|
||||
## 主能力对照表 ← 双挂能力必填
|
||||
## 常见卡住怎么办
|
||||
```
|
||||
|
||||
### 2.1 与入口定位表的关系
|
||||
|
||||
| §0.1 / actions | TUTORIAL 要求 |
|
||||
|----------------|---------------|
|
||||
| 任一能力挂数据管理(toolbar/row/batch/replaces) | **必须**有「入口一:数据管理」专章 |
|
||||
| 任一能力挂 `agent` | **必须**有「入口二:对话任务」专章(**禁止**用「或者你也可以说…」替代专章) |
|
||||
| 任一能力挂 `cron` | **必须**有「入口三」且写清如何建定时、选哪个 Action |
|
||||
| **无** `cron` | **必须**写「本技能不需要定时」+ 一句理由(与 §0.1 一致) |
|
||||
| 某能力只挂一端 | 对照表注明「仅数据管理」或「仅对话」 |
|
||||
|
||||
### 2.2 平行关系(数据管理 ∥ 对话)
|
||||
|
||||
- 两者是**平行主入口**,不是「对话主、点表辅」或反过来
|
||||
- 禁止全文只有「第一步:打开对话说清目标」且无数据管理专章
|
||||
- 允许用户只用其中一种;教程须写清「两种效果一致,选你习惯的」
|
||||
|
||||
---
|
||||
|
||||
## 3. 入口一:数据管理(怎么写)
|
||||
|
||||
适合写:
|
||||
|
||||
1. 左侧有哪些业务表、各干什么(业务语言)
|
||||
2. 首次准备(载入/导入/新建)——**按钮必须出现在用户能看到的地方**(见 §5)
|
||||
3. 主流程:进哪张表 → 是否要选中行 → 点哪个 `label` → 去哪看结果
|
||||
4. 行级 / 批量操作(若有 `row` / `batch`)
|
||||
|
||||
按钮中文名 = `assets/actions.json` 的 `label`,不要另造别名。
|
||||
|
||||
---
|
||||
|
||||
## 4. 入口二:对话任务(怎么写)
|
||||
|
||||
适合写:
|
||||
|
||||
1. 什么时候用对话(与数据管理互补)
|
||||
2. 示例话术表(与主能力一一对应)
|
||||
3. Agent 可能追问什么、用户怎么答
|
||||
|
||||
**不要**把对话写成唯一路径;也不要把话术塞进 README。
|
||||
|
||||
---
|
||||
|
||||
## 5. 准确性铁律(用户看得见才写)
|
||||
|
||||
教程步骤必须对应 **宿主真实 UI**,不是只对应 manifest 声明。
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| **placements 对齐** | 教程写的入口 ∈ `actions.json` 已挂 placement |
|
||||
| **skill-detail 不可作主路径** | 声明了 `skill-detail` **不等于**详情页一定渲染出按钮;当前宿主常只显示四 Tab。**禁止**把「技能详情 → 点某某」写成用户主路径 |
|
||||
| **准备/载入/导入类** | 优先挂 **数据管理 `toolbar`** + `bind.tables`(空表也可点的能力尤其如此);可保留 `agent`;`skill-detail` 仅作可选兼容 |
|
||||
| **写入后刷新** | 建议 `steps: [{ "when": "afterSuccess", "do": "refreshCatalog" }]`,避免用户点完表不刷新 |
|
||||
| **对照表** | 「想做什么 \| 数据管理 \| 对话」覆盖双挂主能力;`label` 与界面一致 |
|
||||
|
||||
字段细节见 [`../references/ACTIONS.md`](../references/ACTIONS.md)「用户可见性与教程准确性」。
|
||||
|
||||
---
|
||||
|
||||
## 6. 入口三:定时任务(可选)
|
||||
|
||||
### 6.1 何时挂 `cron`(教程教「怎么用定时」)
|
||||
|
||||
- 参数相对固定、按节奏重复(同步、巡检、消费队列)
|
||||
- 无人值守仍安全、可幂等或可失败重试
|
||||
- 与数据管理是「白天点选 + 夜里自动啃」组合
|
||||
|
||||
教程写:侧栏定时任务 → 新建 → 选本技能 → 选 Action → 填参 → 保存/立即运行。
|
||||
|
||||
### 6.2 何时不挂(教程写「不需要」)
|
||||
|
||||
- 一次性 / 课堂演示,对象当场指定
|
||||
- 强不可逆、须当场确认
|
||||
- 每次对象不同、须人指定链接或范围
|
||||
- 主路径依赖验证码 / 人工介入
|
||||
|
||||
决策三问(与 [`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md) §2.3 一致):
|
||||
|
||||
1. 明天人不盯着、同样参数再跑一遍,业务上合理吗?
|
||||
2. 失败后自动重跑可接受吗?
|
||||
3. 有没有须人当场确认或动态指定的输入?
|
||||
|
||||
①是 且 ②可接受 且 ③否 → 可挂 cron;否则不挂,§0.1 写理由,教程声明 N/A。
|
||||
|
||||
---
|
||||
|
||||
## 7. 复制后自检(发布前)
|
||||
|
||||
- [ ] README 无分步操作、无 Agent 话术块
|
||||
- [ ] TUTORIAL 有三入口结构(定时有或 N/A)
|
||||
- [ ] 对照表与 `actions.json` `label` 一致
|
||||
- [ ] 准备类按钮在数据管理表顶可见(若技能有数据管理主路径)
|
||||
- [ ] 教程未把 skill-detail 当唯一载入/主流程入口
|
||||
- [ ] §0.1「挂」的入口在 TUTORIAL 有专章;「不挂」有理由且教程一致
|
||||
|
||||
交活清单见 [`CHECKLIST.md`](CHECKLIST.md) A2-7~A2-11。
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考样例
|
||||
|
||||
已验收参考(业务技能,非模板正文):`client-gdcm/review-sku-selection-default`
|
||||
(说明 Tab 瘦身 + 教程三入口 + 「载入课堂样例」挂 `selection_cases` toolbar。)
|
||||
|
||||
复制模板后**不要**照搬选品表名与样例编码;只复用**章节结构与职责划分**。
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
- 宿主入口何时挂:[`PRODUCT_SURFACES.md`](PRODUCT_SURFACES.md)
|
||||
- 入口定位表:[`REQUIREMENTS.md`](REQUIREMENTS.md) §0.1
|
||||
- Action placements / 可见性:[`../references/ACTIONS.md`](../references/ACTIONS.md)
|
||||
- 市场四 Tab 同步:[`DEVELOPMENT.md`](DEVELOPMENT.md) §6 附近「用户市场四 Tab」
|
||||
- 交活验收:[`CHECKLIST.md`](CHECKLIST.md) §2
|
||||
@@ -107,10 +107,10 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
| entrypoint | `type = cli`、`command`、`args`(**必填** JSON 数组,项为 `string` / `number` / `boolean`) |
|
||||
| placements | `toolbar`、`row`、`batch`、`skill-detail`、`agent`、`cron`;仅 `replaces` 时可为空数组 |
|
||||
| **replaces** | 可选 `"create"` \| `"update"` \| `"delete"` \| `"import"` — 接管默认新增/编辑/删除/导入(须 `bind.tables`) |
|
||||
| **bind** | `{ "tables": ["business_table"] }`;可选 `inputMapping`(行内/批量预填,见下节) |
|
||||
| **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`(`textarea` / `date` / `datetime_unix_seconds` / `url` / `email` / `lookup`);可选 **`visibleWhen`**(字段条件显隐,见专节) |
|
||||
| inputSchema | 根 `type: object`;properties 使用 `string` / `number` / `boolean`;支持 `enum`、`default`、`required`、`sensitive`、`readOnly`;可选 `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` 混淆 |
|
||||
@@ -132,15 +132,25 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
|
||||
规则:
|
||||
|
||||
- `bind` 为 object,`additionalProperties: false`;当前允许 `tables`(必填键当存在 bind 时)与可选 `inputMapping`。
|
||||
- `bind` 为 object,`additionalProperties: false`;当前允许 `tables`(必填键当存在 bind 时)与可选 `inputMapping`、`columnMapping`、**`labelColumn`**。
|
||||
- `tables` 非空数组、元素唯一、英文 **snake_case**、不得为空字符串。
|
||||
- **`placements` 含 `toolbar` 时必须显式声明非空 `bind.tables`**(模板 Schema `if/then` + 自动测试强制)。
|
||||
- `row` / `batch` **建议**同样声明 `bind.tables`,避免在无关表上露出按钮;未声明时宿主可能按兼容逻辑在更多表上展示。
|
||||
- 表名须真实存在于技能 SQLite 与 `_jiangchang_tables`。
|
||||
- 希望出现在多张表时,显式列出所有表;**不要**依赖「无 bind 则全表展示」——那是宿主对**旧技能**的兼容,不是新技能标准。
|
||||
- **`inputMapping`(可选)**:把行/勾选映射到 `inputSchema` 字段,例如 `"leadId": "$row.$pk"`、`"ids": "$selection.ids"`。适合 `row` / `batch`。宿主仍会提交这些字段;可用 `inputSchema.properties.*.readOnly: true` 在表单中只读展示。
|
||||
- **`columnMapping`(可选)**:把 inputSchema 属性名映射到物理列名,供表单复用列展示名 / lookup。
|
||||
- **`labelColumn`(可选)**:物理列名,供数据管理 Action 弹层「已选择」摘要展示 `#id + 标签`。**仅影响展示,不改变 CLI/提交 payload,也不决定业务是否跳过某些状态。** 省略时宿主用常见列名启发(如 `title` / `name` / `nickname` 等);再没有则只显示编号。列名不常见、或启发会选错列时再显式声明。
|
||||
- `concurrency` / `locks` 仍未稳定,**不要**写入新技能 manifest。
|
||||
|
||||
### 数据管理:选择摘要(宿主默认能力)
|
||||
|
||||
当用户在数据管理**勾选行**后打开 `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)。
|
||||
|
||||
## `replaces`(可选:接管默认新增 / 编辑 / 删除 / 导入)
|
||||
|
||||
有领域副作用的表(归一化字段、建目录、加密、级联清理)不要依赖宿主直写 SQLite。此时可为该表声明 `replaces`,**用户仍点宿主默认按钮**,由技能 CLI 完成写入。
|
||||
@@ -329,16 +339,16 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
|
||||
| 值 | 数据管理体验 | 典型场景 |
|
||||
|----|--------------|----------|
|
||||
| `direct` | **一点即跑**,无弹窗 | 打开文件位置、无参只读、行内主键已映射 |
|
||||
| `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`) | 打开浏览器、有副作用但无可编辑入参 |
|
||||
| `form` | 完整参数表单(现状默认路径) | 导入、查询条件、需用户改写的字段 |
|
||||
| `direct` | **一点即跑**,无弹窗 | 打开目录、打开浏览器、无参只读、行内主键已映射 |
|
||||
| `confirm` | 轻量确认弹窗(无参数区;文案优先 `confirmation.message`;有勾选时宿主展示「已选择」摘要) | 处理待发送、删除前确认、有副作用但无可编辑入参 |
|
||||
| `form` | 完整参数表单(有勾选时同样展示「已选择」摘要) | 导入、查询条件、需用户改写的字段 |
|
||||
| `auto` / 省略 | 宿主推断:有**可编辑**参数 → `form`;仅有 `confirmation` → `confirm`;否则 → `direct` | 旧技能兼容;新技能建议显式写出 |
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "reveal-profile",
|
||||
"label": "打开文件位置",
|
||||
"description": "在资源管理器中打开 Profile 目录。",
|
||||
"label": "打开目录",
|
||||
"description": "打开该账号的浏览器 Profile 目录。",
|
||||
"placements": ["row"],
|
||||
"executionProfile": "sync",
|
||||
"interaction": "direct",
|
||||
@@ -365,10 +375,10 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
{
|
||||
"id": "open-browser",
|
||||
"label": "打开浏览器",
|
||||
"description": "用该账号持久化 Profile 打开系统浏览器,仅供肉眼核对。",
|
||||
"placements": ["row"],
|
||||
"executionProfile": "async",
|
||||
"interaction": "confirm",
|
||||
"confirmation": { "message": "将用该账号 Profile 打开浏览器,仅供查看。" },
|
||||
"executionProfile": "sync",
|
||||
"interaction": "direct",
|
||||
"bind": {
|
||||
"tables": ["accounts"],
|
||||
"inputMapping": { "accountId": "$row.$pk" }
|
||||
@@ -377,7 +387,7 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
"type": "object",
|
||||
"required": ["accountId"],
|
||||
"properties": {
|
||||
"accountId": { "type": "number", "readOnly": true }
|
||||
"accountId": { "type": "number", "title": "账号 ID", "readOnly": true }
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
@@ -396,6 +406,39 @@ Action 是否提供、挂哪些 `placements`,由本技能 **入口定位表**
|
||||
- **cron 触发 / agent**:忽略 `interaction`(无人值守 / 模型填参);`confirmation` 对 agent 仍作 `requiresConfirmation` 元数据。
|
||||
- 不要用 `interaction: "direct"` 掩盖需要用户填写的可编辑字段——宿主在 `auto` 下会因可编辑参数回落到 `form`;显式 `direct` + 可编辑字段时参数取 default/预填,作者须自觉。
|
||||
|
||||
## Action 按钮中文名(`label`,参考)
|
||||
|
||||
`label` 是数据管理 **toolbar / row / batch** 以及技能详情直调上用户看见的短按钮名。本节是**参考契约,不是白名单**:行业专有动作(私信、审单、报关登记等)按业务起名。同类通用动作尽量用同一动词,避免每个技能一套叫法。
|
||||
|
||||
**写法:** 短中文,动词或「动词 + 对象」(约 2~8 字)。同一能力用**一个** Action 换 `placements`,不要为入口复制同义按钮。
|
||||
|
||||
以后增补:在下表加行,并在模板 `CHANGELOG.md` 记一笔。废弃说法挪到「不要叫」,不要直接删行。
|
||||
|
||||
### 宿主已有,技能不要再做一套同义按钮
|
||||
|
||||
数据管理宿主已提供:**查看 / 编辑 / 删除 / 新增 / 导入 / 导出 / 刷新**。
|
||||
|
||||
技能要用 CLI 接管增删改导入时用 `replaces`(见上文),**不要**再把同义按钮挂到 `placements: ["row"]` 或 `toolbar`(例如「编辑平台」「删除 Key」与底下宿主「编辑」「删除」叠两套)。用户仍点宿主那几个字。
|
||||
|
||||
### 建议动词(有则用之)
|
||||
|
||||
| 类型 | 建议 `label` | 不要叫 | 说明 |
|
||||
|------|----------------|--------|------|
|
||||
| 基础设施 | 运行检查、查看版本、查看配置位置、初始化数据 | 健康检查、版本信息、配置路径、初始化数据库 | 多挂 `skill-detail` |
|
||||
| 打开 | 打开浏览器、打开目录、打开文件 | 打开文件位置、在资源管理器中打开、用浏览器打开 | 见下节静默成功 |
|
||||
| 改库 | 新增…、编辑…、删除…、启用/停用、设置默认…、加入待…、重置为待… | 表记录用「添加」;无必要拆成「启用」「停用」两个按钮 | 对象名可带,如「新增账号」 |
|
||||
| 外源入库 | 同步数据、全量同步、采集…、扫描… | 把宿主「刷新」叫成同步;把导出 CSV 叫成同步 | 默认用「同步数据」;全量才写「全量同步」 |
|
||||
| 队列与探测 | 处理待…、测试 API、测试网页、抽检本条、抽检所选、批量抽检 | 行 / 勾选 / 工具栏三个入口都叫「抽检」 | 行=本条,勾选=所选,不管勾选的批量=批量 |
|
||||
| 生成发布 | 生成 HTML、生成…、发布到… | 用「导出」代替宿主表导出 | 固定 id `render-html` 的 label 用「生成 HTML」 |
|
||||
|
||||
### 打开类:一点即开,成功不提示
|
||||
|
||||
打开目录 / 打开浏览器 / 打开文件:`interaction: "direct"`,`executionProfile` 通常 `"sync"`。
|
||||
|
||||
- **成功**:不要输出用户可见成功文案。若打印 JSON,令 `"message": ""`(宿主据此不弹「执行成功」)。操作系统已经弹出目录或浏览器,就是反馈。
|
||||
- **失败**:仍 `ERROR:<CODE>:`(可加 `HINT:`)。
|
||||
- **不要**用宿主「查看」表示打开目录或浏览器——「查看」是只读详情。
|
||||
|
||||
## `hoverHint`(工具栏悬停,可选)
|
||||
|
||||
与 `label` / `description` / `confirmation.message` **独立**。只决定数据管理**顶栏技能按钮**鼠标悬停时显示什么。
|
||||
@@ -584,6 +627,7 @@ python {skill_root}/scripts/main.py <command> <args...>
|
||||
|
||||
- 使用稳定的退出码
|
||||
- 成功时输出可解析的结构化结果
|
||||
- **例外(打开类)**:打开目录 / 打开浏览器 / 打开文件成功时不要输出用户可见成功文案;若打印 JSON,令 `"message": ""`。失败仍走下一款错误约定
|
||||
- 失败时输出稳定错误信息
|
||||
- 对可预期错误提供稳定错误码
|
||||
- 对用户可处理的问题提供 `HINT`
|
||||
@@ -610,6 +654,18 @@ HINT: <next step>
|
||||
| `agent` | Agent 可直接调用(`run_skill_action`) |
|
||||
| `cron` | 定时任务「技能直调」 |
|
||||
|
||||
### 用户可见性与教程准确性
|
||||
|
||||
`placements` 决定 Action **可以**出现在哪;教程只能写用户在宿主里**实际能点到**的路径。完整用户文档契约见 [`../development/TUTORIAL_SURFACES.md`](../development/TUTORIAL_SURFACES.md)。
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| **skill-detail ≠ 一定可见** | 声明 `skill-detail` 不保证详情页渲染出按钮;当前宿主详情常只有说明/教程/演示/更新日志四 Tab。**禁止**把「技能详情 → 点某某」写成用户主路径或教程唯一入口 |
|
||||
| **准备 / 载入 / 导入 / 重置** | 若技能以数据管理为主路径,此类能力优先 `toolbar` + 非空 `bind.tables`(空表也可点的准备动作尤其如此);可同时挂 `agent`;`skill-detail` 仅作可选兼容 |
|
||||
| **写入后刷新** | 会写入业务表的 Action 建议 `steps: [{ "when": "afterSuccess", "do": "refreshCatalog" }]`,避免用户点完表不更新 |
|
||||
| **教程按钮名** | 必须等于本文件 `label`;对照表与数据管理顶栏/行内文案一致 |
|
||||
| **README vs 教程** | 说明 Tab 不写分步;操作步骤只写 `TUTORIAL.md` |
|
||||
|
||||
### 行级入口 `row`(宿主呈现契约)
|
||||
|
||||
- **技能决定有什么**:`placements` 含 `"row"`(且通过 `bind.tables` / policy 等过滤)的 Action,构成该表行的技能菜单项集合;配什么就出现什么。
|
||||
@@ -655,7 +711,7 @@ HINT: <next step>
|
||||
| 数据同步(外源 → 本地库) | sync 或 async | toolbar + cron + agent + skill-detail(按需) | 可选 |
|
||||
| 单行重试 / 行内处理 | sync 或 async | row(可加 agent) | 可选 |
|
||||
| 批量处理勾选行 | sync 或 async | batch(可加 toolbar/cron) | 可选 |
|
||||
| 导入 / 重置 | sync 或 async | skill-detail, agent | 可选 |
|
||||
| 导入 / 重置 | sync 或 async | **优先** toolbar(须 `bind.tables`)+ agent;skill-detail 仅可选 | 可选 |
|
||||
| 单条副作用 RPA | async(建议) | agent, skill-detail | **建议** |
|
||||
| 批量顺序 pick RPA | async(建议) | toolbar, cron, agent | **建议** |
|
||||
## 同步数据 vs 导出当前表 vs 特殊业务报告 vs HTML 页面
|
||||
@@ -706,7 +762,25 @@ HINT: <next step>
|
||||
| `steps` | `afterSuccess` → **`refreshCatalog`**(重扫 `htmlPages`;不要只 `reloadTable`) |
|
||||
| `entrypoint` | CLI 子命令(如 `render-html`)调用同一写入内核 |
|
||||
|
||||
完整 JSON 形状见 [`../assets/examples/render-html.action.example.json`](../assets/examples/render-html.action.example.json)。最小页面壳见 [`../assets/html/example-report.html`](../assets/html/example-report.html)。
|
||||
完整 JSON 形状见 [`../assets/examples/render-html.action.example.json`](../assets/examples/render-html.action.example.json)。最小页面壳见 [`../assets/html/example-report.html`](../assets/html/example-report.html)(已含数据管理 iframe 预览的布局基线;复制后可按需扩展 `{{BODY}}` 内结构)。
|
||||
|
||||
### 宿主预览布局
|
||||
|
||||
数据管理在 **iframe** 内预览 `html/*.html`;宿主 iframe 已占满右侧预览区,**技能页不应再人为收窄正文**(尤其数据管理窗口通常较窄)。
|
||||
|
||||
| 级别 | 约定 |
|
||||
|------|------|
|
||||
| **必须** | 页面横向**铺满** iframe 可用宽度:`main { width: 100%; min-width: 0; }` |
|
||||
| **必须** | **禁止** `main`(或等价正文容器)使用 `max-width: Npx` 且**无** `margin: 0 auto`(宽屏会左对齐留白;窄宿主窗浪费横向空间) |
|
||||
| **必须** | `html` / `body` 设 `overflow-x: hidden`;滚动以**纵向**为主,避免横向滚动条 |
|
||||
| **必须** | 写入 `{{BODY}}` 的业务数据经 `html.escape`(或等价)转义,防 XSS |
|
||||
| **应当** | `header` 与 `main` 水平内边距一致(如共用 CSS 变量或 `clamp()`) |
|
||||
| **应当** | 多列表格:`width: 100%` + 单元格 `word-break` / `overflow-wrap`,长文本在格内换行 |
|
||||
| **应当** | KPI / 卡片网格用 `repeat(auto-fit, minmax(…, 1fr))` 等流体列,勿死写固定列数 |
|
||||
| **禁止** | 在 `<td>` 上用 `max-width` 限制「说明列」等业务列(易导致列宽异常或横向溢出) |
|
||||
| **禁止** | 依赖外链 CSS/JS 或 `networkidle` 式加载(iframe `sandbox` 仅 `allow-scripts`;页面应自包含) |
|
||||
|
||||
长文阅读型报告若确需版心,可对整个 `main` 使用 `max-width` **且** `margin: 0 auto`,并令 `header` 与 `main` 同宽;**数据看板类**默认不设 `max-width`。
|
||||
|
||||
### 实现要点
|
||||
|
||||
@@ -719,6 +793,7 @@ HINT: <next step>
|
||||
### 验收(复制后自测)
|
||||
|
||||
- [ ] `html/` 下出现 `.html`,数据管理侧栏能看到报告项,预览正常
|
||||
- [ ] 预览区**横向铺满**、**无横向滚动条**;拖窄侧栏后内容自适应(见上节「宿主预览布局」)
|
||||
- [ ] 表顶栏(若声明 toolbar)或报告右键「重新生成」能跑通;成功后预览刷新
|
||||
- [ ] 未登录/无权限用户被拒绝,且有可读错误
|
||||
- [ ] 成功路径带 `refreshCatalog`(或等价让侧栏/预览看到更新)
|
||||
@@ -813,14 +888,18 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
|
||||
| 日期时间 | `display_type` 或 `format`: `datetime_unix_seconds` | 同左;**存/传 Unix 秒** |
|
||||
| URL(薄层) | `display_type` 或 `format`: `url` | `display_type: url` |
|
||||
| Email(薄层) | `display_type` 或 `format`: `email` | `display_type: email` |
|
||||
| **本地文件路径** | `display_type`/`format`: **`file`**;可选 **`pathRoot`** | CRUD 一般不用;见下节 |
|
||||
| **本地目录路径** | `display_type`/`format`: **`directory`**;可选 **`pathRoot`** | CRUD 一般不用;见下节 |
|
||||
| 说明灰字 | `description` | `_jiangchang_columns.description` |
|
||||
| 表格横幅 / 列头「?」 | (无;Action 不用) | `_jiangchang_tables.grid_help` / `_jiangchang_columns.grid_help` |
|
||||
| 默认值 | `default` | SQLite 列 `DEFAULT` |
|
||||
| 只读 | `readOnly: true` | 列 `editable: 0` / 宿主只读规则 |
|
||||
| **条件显隐** | **`visibleWhen`**(见下节) | **本版 CRUD 无对称**;仅 Action / 定时任务参数表单 |
|
||||
| **列筛选「值」** | **无对称**(Action 无表格筛选) | 见 [`SCHEMA.md`](SCHEMA.md)「数据管理:列筛选与全局搜索」:枚举 / boolean / lookup 在筛选栏用**下拉 Select**(非开关、非分段单选);存库仍用 value,展示与搜索认 label |
|
||||
|
||||
**本版不做(勿宣传):** 文件上传、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐。
|
||||
(**Lookup 多选**已支持:同一 `lookup` + `multiple: true`,见上文;不是任意标签数组。)
|
||||
**本版不做(勿宣传):** 浏览器内文件上传控件、复杂 `oneOf` / nested object 专用表单;`visibleWhen` 多条件 AND/OR 树、跨 Action 依赖、CRUD 新增/编辑对称显隐;Action 参数表单的列筛选对称。
|
||||
(**Lookup 多选**已支持:同一 `lookup` + `multiple: true`,见上文;不是任意标签数组。)
|
||||
(**本地路径选择**已支持:`file` / `directory` + 可选 `pathRoot`,见下节;提交值仍是本机绝对路径字符串,不是 multipart 上传。)
|
||||
|
||||
日期时间约定:
|
||||
|
||||
@@ -828,11 +907,71 @@ Skill Action 弹窗与数据管理「新增 / 编辑」使用**同一套控件
|
||||
- `date` 只表示日历日,值为 `YYYY-MM-DD`
|
||||
- 脏/无法归一的旧预设:日期控件可回退为普通文本,避免卡死表单
|
||||
|
||||
### 本地路径选择与 `pathRoot`
|
||||
|
||||
用于数据管理 / 定时任务 **Action 参数表单**里选本机文件或目录(原生对话框)。与宿主工具栏内置「导入」面板(默认打开本技能 `input/`,见 [`../development/DATA_PATHS.md`](../development/DATA_PATHS.md))是**两条 UI 路径**,不要混为一谈。
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `display_type` / `format`: `file` \| `directory` | 渲染「选文件 / 选文件夹」控件;提交值为本机**绝对路径**字符串 |
|
||||
| **`pathRoot`(可选)** | 对话框**起始目录**,相对**当前技能数据根** `{JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/` |
|
||||
|
||||
宿主行为(匠厂已支持):
|
||||
|
||||
| `pathRoot` | 打开对话框时 |
|
||||
|------------|--------------|
|
||||
| 省略 / 空 | `{skill_data}/input` |
|
||||
| `input/库存数据` 等 | `{skill_data}/` + 该相对路径 |
|
||||
| `output` / `output/…` | 落在技能 `output` 下 |
|
||||
| 裸 `库存数据` | 视为 `input/库存数据` |
|
||||
| 绝对路径、盘符、`..` | 非法 → 回退 `{skill_data}/input` |
|
||||
|
||||
硬规则:
|
||||
|
||||
1. **禁止**写用户 ID、盘符、绝对路径;用户与数据根由宿主按当前登录解析。
|
||||
2. **`description` 只给人看**,宿主**不会**解析文案来决定起始目录;要默认进子目录必须写 `pathRoot`。
|
||||
3. Agent / CLI **忽略** `pathRoot`(仍直接传路径字符串)。
|
||||
4. 输入框**已有路径**时,宿主优先用已有值作为起始(便于再次浏览)。
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "refresh-inventory",
|
||||
"label": "刷新库存数据",
|
||||
"description": "从输入目录选择 JSON 快照并更新库存。",
|
||||
"placements": ["toolbar", "agent"],
|
||||
"executionProfile": "sync",
|
||||
"interaction": "form",
|
||||
"bind": { "tables": ["sku_inventory"] },
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["file"],
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string",
|
||||
"title": "库存数据文件",
|
||||
"description": "请选择 input/库存数据/ 下的 JSON。",
|
||||
"format": "file",
|
||||
"display_type": "file",
|
||||
"pathRoot": "input/库存数据"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "cli",
|
||||
"command": "refresh-inventory",
|
||||
"args": ["--file", "{{file}}"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
形状参考:[`assets/examples/path-root-file-action.example.json`](../assets/examples/path-root-file-action.example.json)。
|
||||
|
||||
### 当前宿主稳定支持(摘要)
|
||||
|
||||
- 根:`object`;属性:`string` / `number` / `boolean`(`object`/`array` 仅作 JSON 多行编辑兼容,新技能慎用)
|
||||
- `enum` / `default` / `required` / `sensitive` / `readOnly`
|
||||
- `display_type` / `format`:`textarea` | `date` | `datetime_unix_seconds` | `url` | `email` | **`lookup`**
|
||||
- `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`)
|
||||
- 属性级 `description`(表单标签下方灰字说明)
|
||||
- 简单模板替换(`{{fieldName}}`)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
| 根目录 [`CHANGELOG.md`](../CHANGELOG.md) | 市场更新日志 Tab(版本小节 → `changelog`) |
|
||||
| [`SKILL.md`](../SKILL.md) | LLM / OpenClaw 平台技能入口与触发摘要 |
|
||||
| [`CLI.md`](CLI.md) | 共享 Python、命令行入口、参数与短操作契约 |
|
||||
| [`ACTIONS.md`](ACTIONS.md) | Skill Action、sync/async、任务中心、多入口(按入口定位表挂载;先读 `development/PRODUCT_SURFACES.md`) |
|
||||
| [`ACTIONS.md`](ACTIONS.md) | Skill Action、sync/async、任务中心、多入口、按钮中文名 `label` 参考(按入口定位表挂载;先读 `development/PRODUCT_SURFACES.md`) |
|
||||
| [`SCHEMA.md`](SCHEMA.md) | 本地 SQLite 与数据管理展示契约 |
|
||||
| [`POLICY.md`](POLICY.md) | Data Policy:可选身份授权、Snapshot、行 scope(多数技能 L0 不配) |
|
||||
| [`CELL_PRESENTATION.md`](CELL_PRESENTATION.md) | 数据管理单元格呈现:语义 tone 标色/强调(用途不限;多数技能 P0 不配) |
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
- 读取 `_jiangchang_*` 元数据
|
||||
- 读取 SQLite `PRAGMA table_info`
|
||||
- **数据管理侧栏/表列表**:当库中存在 `_jiangchang_tables` 时,**仅展示**在该表注册且 `visible=1` 的业务表;孤儿物理表(有表、无注册元数据)**不得**出现在侧栏,直接访问 schema API 应返回「表不存在」。极老库无 `_jiangchang_tables` 时,宿主可保留物理表枚举 fallback(兼容 `_schema_meta` 时代)
|
||||
- 根据 schema 渲染数据管理页面
|
||||
- 根据 `editable` 推导字段是否可创建或编辑
|
||||
- 在**新增 / 编辑**表单中,若列有 `_jiangchang_columns.description`,在标签下方展示该说明(灰字;空则不显示)
|
||||
@@ -287,7 +288,7 @@ upsert_column_metadata(
|
||||
| `grid_help` | 否 | 数据管理表头「?」;空/未写则不出。与表单 `description` 独立,禁止互拷 |
|
||||
| `display_order` | 否 | 兼容字段,默认保持 `NULL` |
|
||||
| `visible` | 是 | 是否进入数据管理 schema /「列」面板(`0` = **硬隐藏**,用户无法勾回;默认不展示但可勾回请用表级 `default_visible_columns_json`) |
|
||||
| `searchable` | 是 | 是否参与搜索 |
|
||||
| `searchable` | 是 | 是否参与**列筛选**与**顶栏全局搜索**(`0` = 两者均不参与);详见下节「数据管理:列筛选与全局搜索」 |
|
||||
| `editable` | 是 | `1` 业务字段允许宿主用于新增和编辑;`0` 系统/只读字段 |
|
||||
| `display_type` | 否 | 字段渲染类型;稳定取值见下节「表单与展示:稳定字段类型」 |
|
||||
| `options_json` | 否 | 枚举选项 JSON |
|
||||
@@ -382,6 +383,76 @@ seed_column_metadata_batch(
|
||||
|
||||
Action 参数表单的 `title` / `description` / `default` / `display_type` 见 [`ACTIONS.md`](ACTIONS.md);勿与本节 CRUD 配置入口混用。
|
||||
|
||||
#### 数据管理:列筛选与全局搜索(宿主支持)
|
||||
|
||||
数据管理表格提供两类与 `searchable` 相关的检索能力,技能通过 `_jiangchang_columns.searchable` 声明是否参与:
|
||||
|
||||
| 能力 | 用户入口 | `searchable = 1` 时 | `searchable = 0` 时 |
|
||||
|------|----------|---------------------|---------------------|
|
||||
| **列筛选** | 表格「筛选」→ 字段 / 操作符 / 值 | 出现在「字段」下拉,可按类型选值与操作符 | 不出现在列筛选字段列表 |
|
||||
| **全局搜索** | 顶栏搜索框 | 参与 OR 模糊匹配(宿主对参与列数有上限,如最多 8 列) | 不参与全局搜索 |
|
||||
|
||||
**存库 vs 展示(核心契约)**
|
||||
|
||||
- **列表**:有 `options_json` 时展示 **label**(如「启用」「停用」)
|
||||
- **筛选 / 查询 / 全局搜索**:比较与匹配 **value**(如 `0`/`1`、`active`、`toutiaohao`)
|
||||
- 宿主负责 label ↔ value 映射;**禁止**为迁就 UI 把存库改成中文枚举,也**禁止**在 `description` 里写「筛选请填 0/1」
|
||||
|
||||
**筛选栏控件 ≠ 新增/编辑表单(勿混用)**
|
||||
|
||||
列筛选「值」与表单使用同一套**类型语义**,但控件形态不同:筛选栏须与「字段」「操作符」同一行工具栏对齐(如统一 `h-9` 下拉),**枚举 / boolean / lookup 在筛选栏一律用下拉 Select**,不用新增/编辑表单里的开关或横向分段单选。表单侧仍按上表「新增/编辑控件」配置。
|
||||
|
||||
| 列类型 | 筛选「值」控件 | 常用操作符(宿主按类型裁剪) | 技能须配置 |
|
||||
|--------|----------------|------------------------------|------------|
|
||||
| 文本 / `url` / `email` | 文本框 | 等于 / 包含 / 开头 / 结尾 / 为空… | 通常 `searchable: 1` |
|
||||
| 数字 | 数字框 | 等于 / 大于 / 小于… | `display_type: number` 或 INT/REAL |
|
||||
| 枚举(`options_json` **数组**) | 下拉(展示 label) | 等于 / 不等于 / 为空 | **`options_json` 必填** + `searchable: 1` |
|
||||
| `boolean` | 下拉(有 `options_json` 用其 label;否则宿主默认可选「是/否」) | 等于 / 不等于 / 为空 | `display_type: boolean`;**推荐同时写 `options_json`** 指定中文 label |
|
||||
| `lookup` 单选 | 下拉 | 等于 / 不等于 / 为空 | `display_type: lookup` + lookup 对象 |
|
||||
| `lookup` + `multiple: true` | 列筛选 UI 演进中;**全局搜索**已支持 | 等于时按逗号分隔 id 的 token 匹配 | 多选存 `TEXT` 逗号分隔 id |
|
||||
| `date` / `datetime_unix_seconds` | 日期 / 日期时间 | 比较类 | 时间列通常 `searchable: 0` |
|
||||
| 敏感列 | **不可筛** | — | 宿主强制排除 |
|
||||
|
||||
**全局搜索扩展(用户可搜中文)**
|
||||
|
||||
宿主在 `searchable = 1` 的列上除原文 `LIKE` 外,还应:
|
||||
|
||||
- 有 `options_json` 枚举:**label 或 value** 均可命中
|
||||
- `boolean` 无 options:认「是/否」等常见别名
|
||||
- `lookup` 多选:按逗号分隔 id 的 **token 边界** 匹配;选项 label 亦可参与
|
||||
- `datetime_unix_seconds`:搜 `YYYY-MM-DD` 形态可匹配当天 Unix 秒区间
|
||||
|
||||
**技能配置建议**
|
||||
|
||||
推荐:
|
||||
|
||||
- 用户常筛的状态列(启用/停用、待生成/已生成、`status` 等)→ `searchable: 1` + `options_json`(value 与 DDL 存库一致)
|
||||
- `boolean` 业务列:`display_type: boolean` **且** `options_json` 写中文 label(示例见下)
|
||||
|
||||
禁止:
|
||||
|
||||
- 仅为筛选把存库 value 改成中文
|
||||
- 在文档或 `description` 要求用户记内部 magic number / 英文 code
|
||||
|
||||
示例(启用开关 + 中文筛选):
|
||||
|
||||
```python
|
||||
{
|
||||
"column_name": "enabled",
|
||||
"display_name": "是否启用",
|
||||
"visible": 1,
|
||||
"searchable": 1,
|
||||
"editable": 1,
|
||||
"display_type": "boolean",
|
||||
"options_json": json.dumps(
|
||||
[{"value": 0, "label": "停用"}, {"value": 1, "label": "启用"}],
|
||||
ensure_ascii=False,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
Action 参数表单**无**列筛选对称能力;筛选语义以本节为准,表单对照见 [`ACTIONS.md`](ACTIONS.md)「表单字段类型对照」。
|
||||
|
||||
## editable 与 createable 的关系
|
||||
|
||||
技能模板只维护 `editable` 这一份字段权限声明:
|
||||
@@ -700,6 +771,48 @@ seed_column_metadata_batch(
|
||||
|
||||
不要直接修改用户真实数据库作为迁移方式;元数据初始化必须使用真实物理字段列表。
|
||||
|
||||
## 表增删改与退役(技能 schema 迁移)
|
||||
|
||||
技能拥有 SQLite 物理 schema;宿主只渲染**已注册**的元数据。增删改表时技能须在 `init_db()` 中做幂等迁移,并与 `_jiangchang_*` 同步。
|
||||
|
||||
| 操作 | 技能必须做 |
|
||||
|------|------------|
|
||||
| **新增**用户可见表 | `CREATE TABLE` + `upsert_table_metadata` + 列元数据种子 |
|
||||
| **修改**表/列 | 物理迁移(`ALTER` / 重建)+ 更新或 `prune_stale_column_metadata` |
|
||||
| **临时隐藏**(表仍保留) | 元数据 `visible=0`(表与元数据行都保留) |
|
||||
| **永久废弃**用户可见表 | `drop_deprecated_user_table(...)`(见下) |
|
||||
|
||||
### 永久废弃用户可见表
|
||||
|
||||
使用 `scripts/db/display_metadata.py` 的 **`drop_deprecated_user_table`**(幂等):
|
||||
|
||||
1. `DROP TRIGGER`(若有 `{table}_set_updated_at` 等)
|
||||
2. `DROP TABLE IF EXISTS`
|
||||
3. `DELETE` 对应 `_jiangchang_columns` / `_jiangchang_tables` 行
|
||||
|
||||
```python
|
||||
from db.display_metadata import drop_deprecated_user_table
|
||||
|
||||
def init_db() -> None:
|
||||
...
|
||||
drop_deprecated_user_table(cur, "task_logs")
|
||||
init_display_metadata(cur)
|
||||
```
|
||||
|
||||
**反模式(禁止):** 只删 `_jiangchang_*` 元数据、保留物理表。宿主虽可能不展示孤儿表,但库内残留、自检失败,且违背「元数据与物理结构一致」。
|
||||
|
||||
**新技能禁止**创建或写入 `task_logs` 一类通用任务审计表。过程 → `jiangchang.log` / 运行日志;进度/终态 → 任务中心;业务结果 → 业务表(见 [`WORK_ITEM_STATUS.md`](WORK_ITEM_STATUS.md)、[`../development/LOGGING.md`](../development/LOGGING.md))。
|
||||
|
||||
### 旧库迁移测试(废弃表时必做)
|
||||
|
||||
复制 `tests/samples/test_table_deprecation_migration.py.sample`,构造「旧库含废弃表 + 元数据 + 历史行」→ `init_db()` 后断言:
|
||||
|
||||
- `sqlite_master` 无该表
|
||||
- `_jiangchang_*` 无残留行
|
||||
- `iter_user_tables` 不含该表
|
||||
|
||||
卸载技能**不删**用户数据目录;重装会复用旧库,迁移必须在 `init_db()` 中完成(见 [`../development/DATA_PATHS.md`](../development/DATA_PATHS.md))。
|
||||
|
||||
## 中文命名质量
|
||||
|
||||
- 使用普通用户能理解的业务用语:`error_msg` → **错误信息**,`created_at` → **创建时间**。
|
||||
@@ -708,11 +821,12 @@ seed_column_metadata_batch(
|
||||
|
||||
## 元数据同步规则
|
||||
|
||||
1. 新增用户可见业务表 → 同步 `_jiangchang_tables`。
|
||||
1. 新增用户可见业务表 → 同步 `_jiangchang_tables`(`visible=1` 方可在数据管理展示)。
|
||||
2. 新增用户可见字段 → 同步 `_jiangchang_columns`。
|
||||
3. 删除/重命名表或字段 → 同步清理或迁移元数据。
|
||||
3. 删除/重命名表或字段 → 物理迁移 + 同步清理元数据;**废弃表**用 `drop_deprecated_user_table`,禁止只删元数据留物理表。
|
||||
4. 初始化脚本必须幂等。
|
||||
5. 元数据中的表、字段必须在库中真实存在;可见项不得缺少中文 `display_name`。
|
||||
6. `init_db()` 后:`iter_user_tables` 中每个物理业务表都应有对应 `_jiangchang_tables.display_name`,或已被 `drop_deprecated_user_table` 物理删除。
|
||||
|
||||
## 开发自检
|
||||
|
||||
|
||||
@@ -277,6 +277,32 @@ def init_display_metadata(cursor) -> None:
|
||||
init_display_metadata_tables(cursor)
|
||||
|
||||
|
||||
def drop_deprecated_user_table(
|
||||
cursor,
|
||||
table_name: str,
|
||||
*,
|
||||
triggers: Iterable[str] | None = None,
|
||||
) -> None:
|
||||
"""永久退役用户可见表:删 trigger、DROP 物理表、清理展示元数据(幂等)。
|
||||
|
||||
用于废弃 task_logs 等旧表。禁止只删元数据而保留物理表。
|
||||
见 references/SCHEMA.md「表增删改与退役」。
|
||||
"""
|
||||
for trigger_name in triggers or ():
|
||||
cursor.execute(f"DROP TRIGGER IF EXISTS {trigger_name}")
|
||||
default_updated_at = f"{table_name}_set_updated_at"
|
||||
cursor.execute(f"DROP TRIGGER IF EXISTS {default_updated_at}")
|
||||
cursor.execute(f"DROP TABLE IF EXISTS {_quote_identifier(table_name)}")
|
||||
cursor.execute(
|
||||
"DELETE FROM _jiangchang_columns WHERE table_name = ?",
|
||||
(table_name,),
|
||||
)
|
||||
cursor.execute(
|
||||
"DELETE FROM _jiangchang_tables WHERE table_name = ?",
|
||||
(table_name,),
|
||||
)
|
||||
|
||||
|
||||
def iter_user_tables(cursor) -> Iterable[str]:
|
||||
cursor.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
||||
|
||||
@@ -235,6 +235,50 @@ def validate_options_json(options_json: str | None) -> tuple[list[str], list[str
|
||||
return errors, warnings
|
||||
|
||||
|
||||
def validate_filter_search_metadata(
|
||||
cursor,
|
||||
table_name: str,
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""对可见但未开启 searchable 的枚举 / boolean 列给出软建议。"""
|
||||
warnings: list[str] = []
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT column_name, visible, searchable, display_type, options_json
|
||||
FROM _jiangchang_columns
|
||||
WHERE table_name = ?
|
||||
""",
|
||||
(table_name,),
|
||||
)
|
||||
for column_name, visible, searchable, display_type, options_json in cursor.fetchall():
|
||||
if visible is not None and int(visible) == 0:
|
||||
continue
|
||||
if searchable is not None and int(searchable) != 0:
|
||||
continue
|
||||
|
||||
display_type_norm = str(display_type or "").strip()
|
||||
is_boolean = display_type_norm == "boolean"
|
||||
|
||||
has_enum = False
|
||||
if options_json is not None and str(options_json).strip():
|
||||
opt_errors, _ = validate_options_json(options_json)
|
||||
if not opt_errors:
|
||||
try:
|
||||
parsed = json.loads(str(options_json))
|
||||
except json.JSONDecodeError:
|
||||
parsed = None
|
||||
has_enum = isinstance(parsed, list) and len(parsed) > 0
|
||||
|
||||
if not has_enum and not is_boolean:
|
||||
continue
|
||||
|
||||
warnings.append(
|
||||
f"{table_name}.{column_name}: 枚举/状态类列建议 searchable=1,"
|
||||
"以便宿主列筛选与按中文 label 全局搜索(见 references/SCHEMA.md)"
|
||||
)
|
||||
|
||||
return [], warnings
|
||||
|
||||
|
||||
def validate_column_permissions(
|
||||
cursor,
|
||||
table_name: str,
|
||||
@@ -511,6 +555,9 @@ def validate_display_metadata(conn: sqlite3.Connection, *, check_column_order: b
|
||||
report.errors.extend(perm_errors)
|
||||
report.warnings.extend(perm_warnings)
|
||||
|
||||
_, filter_warnings = validate_filter_search_metadata(cur, table_name)
|
||||
report.warnings.extend(filter_warnings)
|
||||
|
||||
cur.execute("SELECT table_name, column_name FROM _jiangchang_columns")
|
||||
for table_name, column_name in cur.fetchall():
|
||||
if table_name in METADATA_TABLES:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
- 目录:仅一层 `html/*.html`(非递归)
|
||||
- 重新生成 Action id 必须为 `render-html`(宿主硬编码)
|
||||
- 页面布局底线见 `references/ACTIONS.md`「宿主预览布局」;模板壳 `assets/html/example-report.html`
|
||||
- 成功后建议 `steps: refreshCatalog` 以重扫侧栏
|
||||
- 本模块只提供路径/模板/原子写;业务 KPI/表格由各技能自行填充
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
|
||||
|
||||
SKILL_SLUG = "your-skill-slug"
|
||||
SKILL_VERSION = "1.0.95"
|
||||
SKILL_VERSION = "1.0.102"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
||||
|
||||
66
tests/samples/test_table_deprecation_migration.py.sample
Normal file
66
tests/samples/test_table_deprecation_migration.py.sample
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""旧库废弃表迁移样例(复制到 tests/ 并按技能改写)。
|
||||
|
||||
验收:给定旧库含废弃物理表 + 元数据 + 历史行 → init_db() 后表与元数据均清除。
|
||||
见 references/SCHEMA.md「表增删改与退役」。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from _support import IsolatedDataRoot
|
||||
|
||||
|
||||
class TestTableDeprecationMigration(unittest.TestCase):
|
||||
def test_init_db_drops_legacy_task_logs(self) -> None:
|
||||
with IsolatedDataRoot():
|
||||
from db.connection import get_conn, init_db
|
||||
from db.display_metadata import iter_user_tables
|
||||
|
||||
# 1) 模拟升级前的旧库
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE task_logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
task_type TEXT,
|
||||
status TEXT
|
||||
)
|
||||
"""
|
||||
)
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO _jiangchang_tables
|
||||
(table_name, display_name, visible, readonly)
|
||||
VALUES ('task_logs', '任务日志', 1, 0)
|
||||
"""
|
||||
)
|
||||
cur.execute("INSERT INTO task_logs (task_type, status) VALUES ('run', 'ok')")
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# 2) 新代码 init_db() 应幂等清除(在 connection.init_db 中调用 drop_deprecated_user_table)
|
||||
init_db()
|
||||
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_logs'"
|
||||
)
|
||||
self.assertIsNone(cur.fetchone())
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) FROM _jiangchang_tables WHERE table_name='task_logs'"
|
||||
)
|
||||
self.assertEqual(int(cur.fetchone()[0]), 0)
|
||||
self.assertNotIn("task_logs", list(iter_user_tables(cur)))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -74,8 +74,10 @@ def _validate_bind_tables(action_id: str, bind: object) -> list[str]:
|
||||
errors: list[str] = []
|
||||
if not isinstance(bind, dict):
|
||||
return [f"{action_id}: bind must be object"]
|
||||
if set(bind.keys()) - {"tables"}:
|
||||
errors.append(f"{action_id}: bind only allows tables, got {sorted(bind.keys())}")
|
||||
allowed = {"tables", "inputMapping", "columnMapping", "labelColumn"}
|
||||
unknown = set(bind.keys()) - allowed
|
||||
if unknown:
|
||||
errors.append(f"{action_id}: bind only allows {sorted(allowed)}, got {sorted(bind.keys())}")
|
||||
tables = bind.get("tables")
|
||||
if not isinstance(tables, list) or len(tables) < 1:
|
||||
errors.append(f"{action_id}: bind.tables must be non-empty array")
|
||||
@@ -90,6 +92,12 @@ def _validate_bind_tables(action_id: str, bind: object) -> list[str]:
|
||||
if table in seen:
|
||||
errors.append(f"{action_id}: bind.tables duplicate {table!r}")
|
||||
seen.add(table)
|
||||
label_column = bind.get("labelColumn")
|
||||
if label_column is not None:
|
||||
if not isinstance(label_column, str) or not label_column.strip():
|
||||
errors.append(f"{action_id}: bind.labelColumn must be non-empty string")
|
||||
elif not _TABLE_NAME_PATTERN.fullmatch(label_column):
|
||||
errors.append(f"{action_id}: bind.labelColumn {label_column!r} must be snake_case")
|
||||
return errors
|
||||
|
||||
|
||||
@@ -432,6 +440,36 @@ class TestActionsManifest(unittest.TestCase):
|
||||
with self.assertRaises(ValidationError):
|
||||
jsonschema.validate(instance=manifest, schema=schema)
|
||||
|
||||
def test_schema_allows_bind_label_column(self) -> None:
|
||||
try:
|
||||
import jsonschema
|
||||
except ImportError:
|
||||
self.fail("jsonschema is required")
|
||||
schema = _load_actions_schema()
|
||||
bind_props = schema["$defs"]["bind"]["properties"]
|
||||
self.assertIn("labelColumn", bind_props)
|
||||
manifest = {
|
||||
"schemaVersion": 1,
|
||||
"skill": "your-skill-slug",
|
||||
"actions": [
|
||||
{
|
||||
"id": "probe-label-column",
|
||||
"label": "处理勾选",
|
||||
"description": "验证 bind.labelColumn 可通过 Schema",
|
||||
"placements": ["toolbar", "batch"],
|
||||
"executionProfile": "sync",
|
||||
"interaction": "confirm",
|
||||
"bind": {
|
||||
"tables": ["demo_items"],
|
||||
"inputMapping": {"ids": "$selection.ids"},
|
||||
"labelColumn": "display_name",
|
||||
},
|
||||
"entrypoint": {"type": "cli", "command": "health", "args": []},
|
||||
}
|
||||
],
|
||||
}
|
||||
jsonschema.validate(instance=manifest, schema=schema)
|
||||
|
||||
def test_no_execution_profile_placement_coupling_in_tests(self) -> None:
|
||||
"""守护:本文件不得引入入口位置与 sync/async 的错误硬耦合断言。"""
|
||||
path = os.path.join(get_skill_root(), "tests", "test_actions_manifest.py")
|
||||
@@ -593,6 +631,8 @@ class TestActionsManifest(unittest.TestCase):
|
||||
"afterFailure",
|
||||
"cellPresentation",
|
||||
"CELL_PRESENTATION.md",
|
||||
"labelColumn",
|
||||
"已选择",
|
||||
):
|
||||
self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}")
|
||||
self.assertNotIn(
|
||||
|
||||
@@ -57,6 +57,7 @@ POLICY_IDS = (
|
||||
"POLICY-SKILL-ACTION-002",
|
||||
"POLICY-SKILL-ACTION-003",
|
||||
"POLICY-SKILL-ACTION-004",
|
||||
"POLICY-TUTORIAL-001",
|
||||
"POLICY-ARCH-CORE-001",
|
||||
"POLICY-DATA-SYNC-001",
|
||||
)
|
||||
|
||||
@@ -553,6 +553,41 @@ metadata:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_enum_column_without_searchable_warns(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE filter_demo (id INTEGER PRIMARY KEY, enabled INTEGER NOT NULL DEFAULT 1);
|
||||
"""
|
||||
)
|
||||
from db.display_metadata import (
|
||||
init_display_metadata_tables,
|
||||
upsert_column_metadata,
|
||||
upsert_table_metadata,
|
||||
)
|
||||
from db.display_metadata_validator import validate_filter_search_metadata
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_table_metadata(cur, table_name="filter_demo", display_name="筛选演示", readonly=0)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="filter_demo",
|
||||
column_name="enabled",
|
||||
display_name="是否启用",
|
||||
visible=1,
|
||||
searchable=0,
|
||||
editable=1,
|
||||
display_type="boolean",
|
||||
options_json='[{"value":0,"label":"停用"},{"value":1,"label":"启用"}]',
|
||||
)
|
||||
conn.commit()
|
||||
_, warnings = validate_filter_search_metadata(cur, "filter_demo")
|
||||
self.assertTrue(any("searchable=1" in item for item in warnings))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_schema_md_documents_readonly_and_seed_helpers(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "references", "SCHEMA.md")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
@@ -703,6 +738,73 @@ metadata:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_drop_deprecated_user_table_removes_physical_and_metadata(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE _jiangchang_tables (
|
||||
table_name TEXT PRIMARY KEY,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
sort_order INTEGER,
|
||||
visible INTEGER,
|
||||
readonly INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE _jiangchang_columns (
|
||||
table_name TEXT,
|
||||
column_name TEXT,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
display_order INTEGER,
|
||||
visible INTEGER,
|
||||
searchable INTEGER,
|
||||
editable INTEGER,
|
||||
display_type TEXT,
|
||||
options_json TEXT,
|
||||
PRIMARY KEY (table_name, column_name)
|
||||
)
|
||||
"""
|
||||
)
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TABLE task_logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
status TEXT,
|
||||
created_at INTEGER,
|
||||
updated_at INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
cur.execute(
|
||||
"INSERT INTO _jiangchang_tables VALUES ('task_logs', '任务日志', NULL, 1, 1, 0)"
|
||||
)
|
||||
cur.execute(
|
||||
"INSERT INTO _jiangchang_columns VALUES "
|
||||
"('task_logs', 'id', '编号', NULL, 1, 1, 1, 0, 'text', NULL)"
|
||||
)
|
||||
cur.execute("INSERT INTO task_logs (status) VALUES ('success')")
|
||||
|
||||
from db.display_metadata import drop_deprecated_user_table, iter_user_tables
|
||||
|
||||
drop_deprecated_user_table(cur, "task_logs")
|
||||
cur.execute(
|
||||
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_logs'"
|
||||
)
|
||||
self.assertIsNone(cur.fetchone())
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) FROM _jiangchang_tables WHERE table_name='task_logs'"
|
||||
)
|
||||
self.assertEqual(int(cur.fetchone()[0]), 0)
|
||||
self.assertNotIn("task_logs", list(iter_user_tables(cur)))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -181,6 +181,17 @@ class TestDocsStandards(unittest.TestCase):
|
||||
self.assertIn("CHANGELOG.md", text)
|
||||
self.assertIn("demo_video_url", text)
|
||||
self.assertIn("tutorial_md", text)
|
||||
self.assertIn("TUTORIAL_SURFACES.md", text)
|
||||
|
||||
def test_tutorial_surfaces_contract_doc_and_checklist(self) -> None:
|
||||
surfaces = self._read("development/TUTORIAL_SURFACES.md")
|
||||
self.assertIn("说明", surfaces)
|
||||
self.assertIn("教程", surfaces)
|
||||
self.assertIn("skill-detail", surfaces)
|
||||
checklist = self._read("development/CHECKLIST.md")
|
||||
self.assertIn("A2-7", checklist)
|
||||
self.assertIn("A2-8", checklist)
|
||||
self.assertIn("TUTORIAL_SURFACES.md", checklist)
|
||||
|
||||
def test_naming_md_exists_and_covers_standards(self) -> None:
|
||||
text = self._read("development/NAMING.md")
|
||||
@@ -356,6 +367,13 @@ class TestDocsStandards(unittest.TestCase):
|
||||
"跨表引用 lookup",
|
||||
"\"multiple\": true",
|
||||
"Lookup 多选",
|
||||
"数据管理:列筛选与全局搜索",
|
||||
"列筛选与全局搜索",
|
||||
"searchable: 1",
|
||||
"筛选栏控件",
|
||||
"表增删改与退役",
|
||||
"drop_deprecated_user_table",
|
||||
"孤儿物理表",
|
||||
):
|
||||
self.assertIn(marker, text, msg=f"SCHEMA.md missing {marker!r}")
|
||||
|
||||
@@ -375,6 +393,7 @@ class TestDocsStandards(unittest.TestCase):
|
||||
"字段条件显隐",
|
||||
"visibleWhen",
|
||||
"grid_help",
|
||||
"列筛选",
|
||||
):
|
||||
self.assertIn(marker, text, msg=f"ACTIONS.md missing {marker!r}")
|
||||
|
||||
|
||||
@@ -61,6 +61,15 @@ class TestHtmlPageService(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
write_html_page("note.txt", "<html></html>")
|
||||
|
||||
def test_example_report_shell_embedded_preview_layout(self) -> None:
|
||||
"""守护 example-report.html:数据管理 iframe 预览布局基线不退化。"""
|
||||
template = load_html_template("assets/html/example-report.html")
|
||||
self.assertIn("overflow-x: hidden", template)
|
||||
self.assertIn("min-width: 0", template)
|
||||
self.assertIn("table-layout: fixed", template)
|
||||
self.assertIn("overflow-wrap: anywhere", template)
|
||||
self.assertNotIn("max-width:", template)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
198
tests/test_tutorial_surfaces.py
Normal file
198
tests/test_tutorial_surfaces.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""用户市场文档契约:README≠教程、TUTORIAL 三入口结构、与 actions.json 对齐。
|
||||
|
||||
契约全文:development/TUTORIAL_SURFACES.md
|
||||
- 模板占位 slug(your-skill-slug):只检查骨架与契约文档存在
|
||||
- 业务技能:按 actions.json placements 硬校验教程章节与 README 反模式
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from _support import get_skill_root
|
||||
from util.constants import SKILL_SLUG
|
||||
|
||||
TEMPLATE_PLACEHOLDER_SLUG = "your-skill-slug"
|
||||
|
||||
_SECTION_DATA_MGMT = re.compile(r"(?m)^##\s*.*数据管理")
|
||||
_SECTION_DIALOG = re.compile(r"(?m)^##\s*.*(对话任务|对话)")
|
||||
_SECTION_CRON = re.compile(r"(?m)^##\s*.*定时任务")
|
||||
_SECTION_COMPARE = re.compile(r"(?m)^##\s*.*(对照表|主能力)")
|
||||
_SECTION_STUCK = re.compile(r"(?m)^##\s*.*(常见卡住|卡住怎么办)")
|
||||
_SECTION_WHAT = re.compile(r"(?m)^##\s*.*(本技能做什么|做什么)")
|
||||
_NO_CRON_HINT = re.compile(r"不需要定时|未提供定时|不提供定时|不适用.*定时|定时.*不适用")
|
||||
|
||||
# README 反模式:分步操作 / Agent 话术专章(说明 Tab 禁止)
|
||||
_README_HOWTO_AGENT = re.compile(r"(?m)^##\s*.*告诉\s*Agent")
|
||||
_README_ENTRY_CHAPTER = re.compile(r"(?m)^##\s*入口[一二三]")
|
||||
_README_NUMBERED_DM_PATH = re.compile(
|
||||
r"(?m)^\d+[\.、]\s*.*(数据管理|选品任务|表顶).{0,40}(点击|点\s*「)"
|
||||
)
|
||||
|
||||
|
||||
def _read(rel: str) -> str:
|
||||
path = os.path.join(get_skill_root(), rel)
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def _load_actions() -> list[dict]:
|
||||
path = os.path.join(get_skill_root(), "assets", "actions.json")
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
with open(path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return list(data.get("actions") or [])
|
||||
|
||||
|
||||
def _placement_sets(actions: list[dict]) -> tuple[bool, bool, bool]:
|
||||
has_dm = False
|
||||
has_agent = False
|
||||
has_cron = False
|
||||
dm_tokens = frozenset({"toolbar", "row", "batch"})
|
||||
for action in actions:
|
||||
placements = set(action.get("placements") or [])
|
||||
if placements & dm_tokens:
|
||||
has_dm = True
|
||||
if "agent" in placements and str(action.get("id") or "") not in (
|
||||
"health",
|
||||
"version",
|
||||
"config-path",
|
||||
):
|
||||
has_agent = True
|
||||
if "cron" in placements:
|
||||
has_cron = True
|
||||
return has_dm, has_agent, has_cron
|
||||
|
||||
|
||||
class TestTutorialSurfacesContract(unittest.TestCase):
|
||||
def test_tutorial_surfaces_doc_exists(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "development", "TUTORIAL_SURFACES.md")
|
||||
self.assertTrue(
|
||||
os.path.isfile(path),
|
||||
msg="development/TUTORIAL_SURFACES.md must exist (user market doc contract)",
|
||||
)
|
||||
text = _read("development/TUTORIAL_SURFACES.md")
|
||||
for marker in (
|
||||
"README.md",
|
||||
"TUTORIAL.md",
|
||||
"数据管理",
|
||||
"对话",
|
||||
"定时",
|
||||
"skill-detail",
|
||||
"对照表",
|
||||
):
|
||||
self.assertIn(marker, text, msg=f"TUTORIAL_SURFACES.md missing {marker!r}")
|
||||
|
||||
def test_development_md_points_to_tutorial_surfaces(self) -> None:
|
||||
text = _read("development/DEVELOPMENT.md")
|
||||
self.assertIn(
|
||||
"TUTORIAL_SURFACES.md",
|
||||
text,
|
||||
msg="DEVELOPMENT.md must reference TUTORIAL_SURFACES.md for market docs",
|
||||
)
|
||||
|
||||
def test_template_or_business_tutorial_has_required_skeleton(self) -> None:
|
||||
tutorial = _read("TUTORIAL.md")
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_WHAT,
|
||||
msg="TUTORIAL.md must have a '本技能做什么' (or equivalent) section",
|
||||
)
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_DATA_MGMT,
|
||||
msg="TUTORIAL.md must have a 数据管理 section (or 不适用 placeholder)",
|
||||
)
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_DIALOG,
|
||||
msg="TUTORIAL.md must have a 对话 section (or 不适用 placeholder)",
|
||||
)
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_CRON,
|
||||
msg="TUTORIAL.md must have a 定时任务 section (teach or N/A)",
|
||||
)
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_COMPARE,
|
||||
msg="TUTORIAL.md must have a 主能力对照表 section",
|
||||
)
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_STUCK,
|
||||
msg="TUTORIAL.md must have a 常见卡住 section",
|
||||
)
|
||||
# 禁止旧「对话单线」作为唯一主结构标题
|
||||
self.assertNotRegex(
|
||||
tutorial,
|
||||
r"(?m)^##\s*第一步:打开技能并说清目标\s*$",
|
||||
msg="TUTORIAL.md must not use legacy dialogue-only first step as sole structure",
|
||||
)
|
||||
|
||||
def test_readme_has_no_legacy_agent_howto_chapter(self) -> None:
|
||||
readme = _read("README.md")
|
||||
self.assertIsNone(
|
||||
_README_HOWTO_AGENT.search(readme),
|
||||
msg="README.md must not have '告诉 Agent' howto chapter (belongs in TUTORIAL)",
|
||||
)
|
||||
self.assertIsNone(
|
||||
_README_ENTRY_CHAPTER.search(readme),
|
||||
msg="README.md must not have 入口一/二/三 chapters (belongs in TUTORIAL)",
|
||||
)
|
||||
|
||||
def test_business_skill_tutorial_aligns_with_actions(self) -> None:
|
||||
if SKILL_SLUG == TEMPLATE_PLACEHOLDER_SLUG:
|
||||
self.skipTest("template placeholder slug: skip actions alignment hard checks")
|
||||
|
||||
actions = _load_actions()
|
||||
has_dm, has_agent, has_cron = _placement_sets(actions)
|
||||
tutorial = _read("TUTORIAL.md")
|
||||
readme = _read("README.md")
|
||||
|
||||
if has_dm:
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_DATA_MGMT,
|
||||
msg="business skill with data-management placements must document 数据管理 in TUTORIAL",
|
||||
)
|
||||
# 准备类若挂了 toolbar,教程不应把 skill-detail 当唯一主路径
|
||||
self.assertNotRegex(
|
||||
tutorial,
|
||||
r"(?m)^\d+[\.、]\s*.*技能详情.{0,30}点击",
|
||||
msg="TUTORIAL must not use 技能详情点击 as primary numbered path when DM exists",
|
||||
)
|
||||
|
||||
if has_agent:
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_DIALOG,
|
||||
msg="business skill with agent placements must document 对话 in TUTORIAL",
|
||||
)
|
||||
|
||||
if has_cron:
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_SECTION_CRON,
|
||||
msg="with cron placements, TUTORIAL must have 定时任务 section",
|
||||
)
|
||||
else:
|
||||
self.assertRegex(
|
||||
tutorial,
|
||||
_NO_CRON_HINT,
|
||||
msg="without cron placements, TUTORIAL must declare 不需要/未提供定时",
|
||||
)
|
||||
|
||||
# README 不应出现「数据管理 → … → 点击」式编号步骤
|
||||
self.assertIsNone(
|
||||
_README_NUMBERED_DM_PATH.search(readme),
|
||||
msg="README.md must not contain numbered data-management click steps",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user