Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 144455fbe0 | |||
| 5ec9648148 |
68
.env.example
68
.env.example
@@ -1,30 +1,50 @@
|
||||
# ── 运行模式 / adapter 档位(见 development/ADAPTER.md)──
|
||||
OPENCLAW_TEST_TARGET=mock # mock | simulator_rpa | real_api | real_rpa
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
|
||||
# ── 目标系统 ──
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com # 仿真/生产地址,可被进程环境变量覆盖
|
||||
# 目标网站地址:技能要访问的网站或服务地址
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
|
||||
|
||||
# ── 默认账号(仅非敏感标识;密码走 account-manager)──
|
||||
DEFAULT_LOGIN_ID=04110001
|
||||
# 默认登录账号:仅填写非敏感的账号标识(如工号/登录名)
|
||||
DEFAULT_LOGIN_ID=04110001 # 只填账号标识,不要填密码;密码请在「账号管理」中登记
|
||||
|
||||
# ── 浏览器 / RPA ──
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=有头(默认) 1=无头(CI)
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启反检测指纹(默认)
|
||||
# 是否显示浏览器窗口:打开后能否看见自动化操作过程
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(推荐,便于排查),1=后台静默运行
|
||||
|
||||
# ── 存证 ──
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=默认不录屏;1=启用录屏+字幕+旁白+背景音+MP4
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败截图(默认)
|
||||
# 浏览器防检测:降低被网站识别为自动化工具的概率
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启(推荐),0=关闭;一般保持默认
|
||||
|
||||
# ── 节流 / 超时 ──
|
||||
STEP_DELAY_MIN=1.0 # 步骤间随机等待下限(秒)
|
||||
STEP_DELAY_MAX=5.0 # 步骤间随机等待上限(秒)
|
||||
HUMAN_WAIT_TIMEOUT=180 # 滑块/验证码/2FA 等人工超时(秒)
|
||||
# 是否录制操作过程:用于留证、复现问题或向他人演示
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并生成带说明的视频文件;需要留证时再改为 1
|
||||
|
||||
# ── 数据目录子路径(见 development/DATA_PATHS.md;留空则用 skill 数据目录下默认子文件夹)──
|
||||
# 相对路径相对于 {JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/,不是 workspace / CWD
|
||||
# SKILL_DOWNLOAD_DIR=downloads
|
||||
# SKILL_IMPORT_DIR=imports
|
||||
# SKILL_EXPORT_DIR=exports
|
||||
# SKILL_UPLOAD_DIR=uploads
|
||||
# SKILL_CACHE_DIR=cache
|
||||
# SKILL_TEMP_DIR=temp
|
||||
# 失败时是否截图:任务出错时自动保存现场截图,便于排查
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图(推荐),0=不截图
|
||||
|
||||
# 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
|
||||
STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
|
||||
|
||||
# 步骤最长等待:两步操作之间随机停顿的上限
|
||||
STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
|
||||
|
||||
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
|
||||
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
|
||||
|
||||
# 以下为可选目录配置。一般不用改;取消注释并填写后,才覆盖默认子文件夹。
|
||||
# 填写相对路径时,相对于本技能的数据目录(不是当前工作目录)。
|
||||
|
||||
# 下载目录:技能保存下载文件的位置
|
||||
# SKILL_DOWNLOAD_DIR=downloads # 相对本技能数据目录;留空或保持注释则用默认 downloads
|
||||
|
||||
# 导入目录:待导入文件放置的位置
|
||||
# SKILL_IMPORT_DIR=imports # 相对本技能数据目录;留空或保持注释则用默认 imports
|
||||
|
||||
# 导出目录:技能导出文件的保存位置
|
||||
# SKILL_EXPORT_DIR=exports # 相对本技能数据目录;留空或保持注释则用默认 exports
|
||||
|
||||
# 上传目录:待上传文件放置的位置
|
||||
# SKILL_UPLOAD_DIR=uploads # 相对本技能数据目录;留空或保持注释则用默认 uploads
|
||||
|
||||
# 缓存目录:临时缓存文件的位置
|
||||
# SKILL_CACHE_DIR=cache # 相对本技能数据目录;留空或保持注释则用默认 cache
|
||||
|
||||
# 临时目录:运行中临时文件的位置
|
||||
# SKILL_TEMP_DIR=temp # 相对本技能数据目录;留空或保持注释则用默认 temp
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
|
||||
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
|
||||
|
||||
## 1.0.48
|
||||
|
||||
- 新增配置注释硬门禁 `POLICY-CONFIG-004`:`.env.example` 每个配置项必须有中文行头与行尾说明,禁止开发文档指向,测试不通过则无法发布
|
||||
|
||||
## 1.0.47
|
||||
|
||||
- 规范 `.env.example` 注释:面向「数据管理 → 配置管理」用户,行头说明「是什么」、行尾说明「怎么填」
|
||||
- 去掉注释中的开发文档指向与黑话;`CONFIG.md` / 发布检查清单同步约束,避免各技能注释写法不一致
|
||||
|
||||
## 1.0.46
|
||||
|
||||
- 模板默认增加市场材料:`TUTORIAL.md`(使用教程)、`DEMO.md`(演示)、`CHANGELOG.md`(更新日志),与根 `README.md`(技能说明)组成四 Tab 内容源
|
||||
|
||||
2
SKILL.md
2
SKILL.md
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 技能开发模板(通用业务版)
|
||||
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
|
||||
version: 1.0.46
|
||||
version: 1.0.48
|
||||
author: 深圳匠厂科技有限公司
|
||||
metadata:
|
||||
openclaw:
|
||||
|
||||
@@ -11,44 +11,109 @@
|
||||
4. **技能升级合并缺失项**:`merge_missing_env_keys` 会把 `.env.example` 中新增、但用户 `.env` 尚未包含的 key **追加**进去(带注释块)。
|
||||
5. **敏感信息绝不进 `.env`**:密码/密钥/口令走 account-manager 的 `secret-ref`(见下"红线")。
|
||||
|
||||
## 标准配置项(`.env` 分组)
|
||||
## `.env.example` 注释 = 配置管理用户文案(硬规则)
|
||||
|
||||
每个 skill 的 `.env.example` 至少包含这些通用项(按需增减):
|
||||
匠厂宿主「数据管理 → 配置管理」会把 `.env` / `.env.example` 的注释展示给**最终用户**(英文 key 可保留作标题)。因此注释必须按**产品文案**来写,不能按开发备忘录来写。
|
||||
|
||||
### 行头与行尾分工
|
||||
|
||||
| 位置 | 职责 | 要求 |
|
||||
|------|------|------|
|
||||
| **行头**(`KEY=` 上一行的 `# …`) | **是什么**:中文说明该配置项干什么 | **每个活跃 key 必有**一行完整中文行头 |
|
||||
| **行尾**(`KEY=值` 同一行 ` #` 之后) | **怎么填**:可选值、单位、推荐默认、何时要改 | 有枚举、开关、单位时**必有**;纯自由文本也应尽量写清填法 |
|
||||
|
||||
宿主当前会把「行头 + 行尾」拼成 key 下方的多行说明。按上表书写后,用户看到的自然是「先懂是什么,再知道怎么填」。
|
||||
|
||||
**不要**依赖 `# ── 分组 ──` 装饰线给整组做唯一说明:宿主只会把行头挂到**紧挨着的下一个活跃 key**,后面的 key 不会自动继承。分组可用空行区分;**逐 key 自带行头(+ 行尾)才是硬要求**。
|
||||
|
||||
### 必须做到
|
||||
|
||||
1. 每个会进入配置管理的活跃 `KEY=` 都有完整中文**行头**。
|
||||
2. 枚举 / `0|1` 开关 / 带单位的数值:必须有中文**行尾**,解释每个可选值或单位,并给出推荐。
|
||||
3. 业务专属 key 同样适用本规则,不得只留英文 key、不写中文说明。
|
||||
4. 开发向细节(四档 adapter 实现、录屏管线、路径解析 API)写在 `development/ADAPTER.md`、`RPA.md`、`DATA_PATHS.md` 等文档;**禁止**写进 `.env.example` 注释。
|
||||
|
||||
**机器门禁**:`POLICY-CONFIG-004`(`tests/test_development_policy_guard.py`)会在默认测试 / `release.ps1` 中校验上述「行头+行尾中文、禁止开发文档指向」;不通过不得发布。
|
||||
|
||||
### 禁止出现在注释中的内容
|
||||
|
||||
- `见 development/...`、`见 ADAPTER.md`、`见 RPA.md`、`见 DATA_PATHS.md` 等仓库文档指向
|
||||
- 默认假设读者是开发者的黑话:如单独甩 `adapter 档位`、`CI`、`account-manager`、`CWD`、`workspace` 而不解释
|
||||
- 只写裸枚举(如 `mock | real_rpa`)却不说明每个值对用户意味着什么
|
||||
- 多个 key 共用一个行头,后面的 key 没有任何自己的说明
|
||||
|
||||
若必须提到账号体系:写成用户能懂的「请在账号管理中登记密码 / 平台标识须一致」,不要只写内部模块名。
|
||||
|
||||
### 正例
|
||||
|
||||
```ini
|
||||
# ── 运行模式 / adapter 档位(见 ADAPTER.md)──
|
||||
OPENCLAW_TEST_TARGET=mock # mock | simulator_rpa | real_api | real_rpa
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
|
||||
# ── 目标系统(示例占位,复制后改为业务地址)──
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com
|
||||
|
||||
# ── 默认账号(仅非敏感标识;密码走 account-manager)──
|
||||
# 浏览器 RPA simulator_rpa 联调账号来自 account-manager(platform + profile_dir),不是此处的 DEFAULT_LOGIN_ID
|
||||
DEFAULT_LOGIN_ID=04110001
|
||||
|
||||
# ── 浏览器 / RPA ──
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=有头(默认) 1=无头(CI)
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启反检测指纹(默认)
|
||||
|
||||
# ── 录屏与失败存证 ──
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=默认不录屏;1=启用录屏+字幕+旁白+背景音+MP4(见 RPA.md)
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败截图(默认)
|
||||
|
||||
# ── 节流 / 超时 ──
|
||||
STEP_DELAY_MIN=1.0
|
||||
STEP_DELAY_MAX=5.0
|
||||
HUMAN_WAIT_TIMEOUT=180 # 滑块/验证码/2FA 等人工超时(秒)
|
||||
# 是否显示浏览器窗口:打开后能否看见自动化操作过程
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(推荐,便于排查),1=后台静默运行
|
||||
```
|
||||
|
||||
### 反例(禁止)
|
||||
|
||||
```ini
|
||||
# ── 运行模式 / adapter 档位(见 development/ADAPTER.md)──
|
||||
OPENCLAW_TEST_TARGET=real_rpa # 生产默认真实 RPA;单测/CI 可改为 mock
|
||||
|
||||
# ── 好看视频 / 百度账号(须与 account-manager 平台 key 一致)──
|
||||
TARGET_PLATFORM=baidu
|
||||
HAOKAN_VIDEO_SELECTOR=video.art-video
|
||||
```
|
||||
|
||||
问题:文档路径与开发黑话;行尾半给用户半给 CI;后续 key 无行头/行尾,用户只看到英文名。
|
||||
|
||||
## 标准配置项(`.env` 分组)
|
||||
|
||||
每个 skill 的 `.env.example` 至少包含这些通用项(按需增减)。**注释写法须与仓库根目录 `.env.example` 保持同级清晰度**(行头 = 是什么,行尾 = 怎么填):
|
||||
|
||||
```ini
|
||||
# 运行模式:决定本技能用模拟数据还是真实浏览器/接口去执行
|
||||
OPENCLAW_TEST_TARGET=mock # 可选:mock=本地模拟,simulator_rpa=仿真站点,real_api=真实接口,real_rpa=真实浏览器;模板默认 mock,日常正式使用请改为 real_rpa
|
||||
|
||||
# 目标网站地址:技能要访问的网站或服务地址
|
||||
TARGET_BASE_URL=https://sandbox.jc2009.com # 填写完整网址(含 https://);换环境时再改,一般保持默认即可
|
||||
|
||||
# 默认登录账号:仅填写非敏感的账号标识(如工号/登录名)
|
||||
DEFAULT_LOGIN_ID=04110001 # 只填账号标识,不要填密码;密码请在「账号管理」中登记
|
||||
|
||||
# 是否显示浏览器窗口:打开后能否看见自动化操作过程
|
||||
OPENCLAW_BROWSER_HEADLESS=0 # 0=显示窗口(推荐,便于排查),1=后台静默运行
|
||||
|
||||
# 浏览器防检测:降低被网站识别为自动化工具的概率
|
||||
OPENCLAW_PLAYWRIGHT_STEALTH=1 # 1=开启(推荐),0=关闭;一般保持默认
|
||||
|
||||
# 是否录制操作过程:用于留证、复现问题或向他人演示
|
||||
OPENCLAW_RECORD_VIDEO=0 # 0=不录屏(默认),1=录屏并生成带说明的视频文件;需要留证时再改为 1
|
||||
|
||||
# 失败时是否截图:任务出错时自动保存现场截图,便于排查
|
||||
OPENCLAW_ARTIFACTS_ON_FAILURE=1 # 1=失败时截图(推荐),0=不截图
|
||||
|
||||
# 步骤最短等待:两步操作之间随机停顿的下限,模拟真人节奏
|
||||
STEP_DELAY_MIN=1.0 # 单位:秒;一般保持默认,页面不稳定时可略调大
|
||||
|
||||
# 步骤最长等待:两步操作之间随机停顿的上限
|
||||
STEP_DELAY_MAX=5.0 # 单位:秒;须大于等于最短等待;一般保持默认
|
||||
|
||||
# 人工处理超时:遇到滑块、验证码、二次验证时,等待人工处理的最长时间
|
||||
HUMAN_WAIT_TIMEOUT=180 # 单位:秒;来不及处理时可调大,如 300
|
||||
```
|
||||
|
||||
四档运行模式的**实现与测试约定**见 [`ADAPTER.md`](ADAPTER.md);录屏管线见 [`RPA.md`](RPA.md)。这些内容**不要**写回 `.env.example` 注释。
|
||||
|
||||
**业务专属配置必须带技能前缀**(如 `DEMO_XXX`、`MY_SKILL_XXX`、`SKILL_DOWNLOAD_DIR`),**不要污染全局命名空间**(禁止 `SCRAPE_1688_*`、`RECEIVE_ORDER_*` 等跨技能前缀写入模板)。
|
||||
|
||||
### 数据目录子路径(下载 / 导入 / 导出)
|
||||
|
||||
技能**自己写入**的文件默认在 `{JIANGCHANG_DATA_ROOT}/{USER_ID}/{slug}/` 下标准子目录(`downloads/`、`imports/`、`exports/` 等)。须通过 `util.runtime_paths.resolve_data_path()` 或 `get_*_dir()` 解析,**禁止** `os.path.abspath(config.get(...))` 相对 CWD。
|
||||
|
||||
- 权威说明:[`DATA_PATHS.md`](DATA_PATHS.md)
|
||||
- `.env.example` 中路径类配置**留空即用默认**;若写相对路径,必须相对 **skill 数据目录**,**禁止** `./outputs/...` 这类相对 workspace 的写法
|
||||
- 可选 env:`SKILL_DOWNLOAD_DIR`、`SKILL_IMPORT_DIR`、`SKILL_EXPORT_DIR` 等(见 DATA_PATHS.md)
|
||||
- 权威说明(开发):[`DATA_PATHS.md`](DATA_PATHS.md)
|
||||
- `.env.example` 中路径类配置**留空或保持注释即用默认**;若写相对路径,必须相对 **skill 数据目录**,**禁止** `./outputs/...` 这类相对 workspace 的写法
|
||||
- 可选 env:`SKILL_DOWNLOAD_DIR`、`SKILL_IMPORT_DIR`、`SKILL_EXPORT_DIR` 等;若作为活跃 key 出现在配置管理中,同样须有用户向行头/行尾(见模板根 `.env.example`)
|
||||
|
||||
### 录屏开关(`OPENCLAW_RECORD_VIDEO`)
|
||||
|
||||
@@ -110,7 +175,7 @@ config.get_float("STEP_DELAY_MIN", 1.0)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `RPA.md` — 三端 RPA 标准与各开关含义
|
||||
- `ADAPTER.md` — `OPENCLAW_TEST_TARGET` 四档模式
|
||||
- `RPA.md` — 三端 RPA 标准与各开关含义(开发向;勿写入 `.env` 注释)
|
||||
- `ADAPTER.md` — `OPENCLAW_TEST_TARGET` 四档模式(开发向;勿写入 `.env` 注释)
|
||||
- `RUNTIME.md` — `JIANGCHANG_DATA_ROOT` 与数据根约定
|
||||
- `DATA_PATHS.md` — 下载/导入/导出等子目录与 `resolve_data_path` 规范
|
||||
|
||||
@@ -853,6 +853,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
||||
- [ ] `service` 下的核心业务文件(如 `task_service.py`)已按领域改名并实现
|
||||
- [ ] 没有残留旧平台名
|
||||
- [ ] `health` / `version` 可运行
|
||||
- [ ] `.env.example` 注释已按 [`CONFIG.md`](CONFIG.md)「行头 = 是什么、行尾 = 怎么填」写全(面向配置管理用户;无 `见 development/...` 等开发文档指向)
|
||||
- [ ] `.gitignore` 生效,没有把 `__pycache__` 提交进去
|
||||
- [ ] `release.ps1` 存在(发布前会自动跑 `python tests/run_tests.py -v`;失败不得打 tag)
|
||||
- [ ] `.github/workflows/release_skill.yaml` 存在
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
| POLICY-CONFIG-001 | 仓库根目录必须存在 `.env.example` | development/CONFIG.md §核心原则、§标准配置项 | hard | 文件存在性 | `tests/test_development_policy_guard.py::TestPolicyConfig001` |
|
||||
| POLICY-CONFIG-002 | `.gitignore` 必须忽略 `.env` 与 `*.env.local` | development/CONFIG.md §红线:敏感信息不进 .env | hard | 解析 `.gitignore` 行 | `tests/test_development_policy_guard.py::TestPolicyConfig002` |
|
||||
| POLICY-CONFIG-003 | 业务代码不得直接 `os.environ` / `os.getenv` / `os.putenv` / `environ.get`;须经 `jiangchang_skill_core.config.get*`(允许 `main.py`、测试、config_bootstrap/runtime 边界文件) | development/CONFIG.md §配置 bootstrap;development/ADAPTER.md §档位 dispatch | hard | 扫描 `scripts/**/*.py` 并排除边界文件 | `tests/test_development_policy_guard.py::TestPolicyConfig003` |
|
||||
| POLICY-CONFIG-004 | `.env.example` 每个活跃 `KEY=` 必须有中文**行头**(是什么)与中文**行尾**(怎么填);注释禁止 `development/`、`*.md` 文档指向、`account-manager`、`adapter 档位` 等开发黑话 | development/CONFIG.md §.env.example 注释 = 配置管理用户文案 | hard | 解析 `.env.example` 活跃行与注释 | `tests/test_development_policy_guard.py::TestPolicyConfig004` |
|
||||
| POLICY-TESTING-001 | 默认根目录 `tests/test_*.py` 不得硬编码 `real_api` / `real_rpa` / `OPENCLAW_TEST_TARGET`(白名单仅 `test_adapter_profile_policy.py`,用于档位 helper 自测) | development/TESTING.md §默认必跑测试要做什么、§4 测试目标档位 | hard | 扫描 `tests/test_*.py` 非注释行;白名单外禁止上述文本 | `tests/test_development_policy_guard.py::TestPolicyTesting001` |
|
||||
| POLICY-TESTING-002 | `tests/integration/` 下 Python 测试文件若存在,默认应为 `.sample`,不得进入默认套件 | development/TESTING.md §7 真实联调测试的安全约束、§8 新 skill 的最小测试清单 | hard | 检查 `tests/integration/*.py` 无 `.sample` 后缀 | `tests/test_development_policy_guard.py::TestPolicyTesting002` |
|
||||
| POLICY-RPA-001 | 不得 import/use account-manager 内部 `rpa_helpers` / `inject_account_manager_scripts_path` / `get_account_credential` | development/ADAPTER.md §兄弟依赖;development/RPA.md §1.7 | hard | 扫描 `scripts/**/*.py` 禁止模式 | `tests/test_development_policy_guard.py::TestPolicyRpa001`(另见 `tests/test_no_rpa_helpers_import.py`) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
|
||||
|
||||
SKILL_SLUG = "your-skill-slug"
|
||||
SKILL_VERSION = "1.0.46"
|
||||
SKILL_VERSION = "1.0.48"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.0"
|
||||
|
||||
@@ -29,6 +29,7 @@ POLICY_IDS = (
|
||||
"POLICY-CONFIG-001",
|
||||
"POLICY-CONFIG-002",
|
||||
"POLICY-CONFIG-003",
|
||||
"POLICY-CONFIG-004",
|
||||
"POLICY-TESTING-001",
|
||||
"POLICY-TESTING-002",
|
||||
"POLICY-RPA-001",
|
||||
@@ -86,6 +87,22 @@ CONFIG_ENV_FORBIDDEN_PATTERNS = (
|
||||
re.compile(r"\benviron\.get\b"),
|
||||
)
|
||||
|
||||
CONFIG_004_SOURCE = "development/CONFIG.md §.env.example 注释 = 配置管理用户文案"
|
||||
CONFIG_004_ENV_KEY_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
||||
CONFIG_004_CJK_RE = re.compile(r"[\u4e00-\u9fff]")
|
||||
# 注释中禁止出现的开发向指向 / 黑话(大小写不敏感子串)
|
||||
CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS = (
|
||||
"development/",
|
||||
"adapter.md",
|
||||
"rpa.md",
|
||||
"data_paths.md",
|
||||
"config.md",
|
||||
"account-manager",
|
||||
"见 development",
|
||||
"adapter 档位",
|
||||
"adapter档位",
|
||||
)
|
||||
|
||||
TESTING_REAL_TARGET_ALLOWED_ROOT_TESTS = frozenset({"test_adapter_profile_policy.py"})
|
||||
|
||||
TESTING_001_FORBIDDEN_SUBSTRINGS = (
|
||||
@@ -149,6 +166,77 @@ def _policy_msg(policy_id: str, source: str, detail: str) -> str:
|
||||
return f"{policy_id} [{source}]: {detail}"
|
||||
|
||||
|
||||
def _strip_env_quotes(value: str) -> str:
|
||||
trimmed = value.strip()
|
||||
if (
|
||||
(trimmed.startswith('"') and trimmed.endswith('"'))
|
||||
or (trimmed.startswith("'") and trimmed.endswith("'"))
|
||||
):
|
||||
return trimmed[1:-1]
|
||||
return trimmed
|
||||
|
||||
|
||||
def _iter_env_example_active_entries(path: str):
|
||||
"""Yield (lineno, key, value, header_comments, inline_comment) for active KEY= lines."""
|
||||
pending: list[str] = []
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
for lineno, line in enumerate(handle, 1):
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
pending = []
|
||||
continue
|
||||
if stripped.startswith("#"):
|
||||
pending.append(stripped.lstrip("#").strip())
|
||||
continue
|
||||
eq = stripped.find("=")
|
||||
if eq <= 0:
|
||||
pending = []
|
||||
continue
|
||||
key = stripped[:eq].strip()
|
||||
if not CONFIG_004_ENV_KEY_RE.match(key):
|
||||
pending = []
|
||||
continue
|
||||
rest = stripped[eq + 1 :]
|
||||
inline: str | None = None
|
||||
if not rest.startswith('"') and not rest.startswith("'"):
|
||||
hash_at = rest.find(" #")
|
||||
if hash_at >= 0:
|
||||
inline = rest[hash_at + 2 :].strip()
|
||||
rest = rest[:hash_at]
|
||||
value = _strip_env_quotes(rest)
|
||||
yield lineno, key, value, list(pending), inline
|
||||
pending = []
|
||||
|
||||
|
||||
def _config_004_comment_offenders(path: str) -> list[str]:
|
||||
offenders: list[str] = []
|
||||
for lineno, key, _value, headers, inline in _iter_env_example_active_entries(path):
|
||||
if not headers:
|
||||
offenders.append(
|
||||
f".env.example:{lineno}: {key}= 缺少行头注释(须用中文说明「是什么」)"
|
||||
)
|
||||
elif not any(CONFIG_004_CJK_RE.search(part) for part in headers):
|
||||
offenders.append(
|
||||
f".env.example:{lineno}: {key}= 行头注释须含中文(说明「是什么」)"
|
||||
)
|
||||
if not inline:
|
||||
offenders.append(
|
||||
f".env.example:{lineno}: {key}= 缺少行尾注释(须用中文说明「怎么填」)"
|
||||
)
|
||||
elif not CONFIG_004_CJK_RE.search(inline):
|
||||
offenders.append(
|
||||
f".env.example:{lineno}: {key}= 行尾注释须含中文(说明「怎么填」)"
|
||||
)
|
||||
combined = "\n".join([*headers, inline or ""]).lower()
|
||||
for token in CONFIG_004_FORBIDDEN_COMMENT_SUBSTRINGS:
|
||||
if token.lower() in combined:
|
||||
offenders.append(
|
||||
f".env.example:{lineno}: {key}= 注释禁止含 {token!r}(面向用户,勿写开发文档指向/黑话)"
|
||||
)
|
||||
break
|
||||
return offenders
|
||||
|
||||
|
||||
def _rel(skill_root: str, path: str) -> str:
|
||||
return os.path.relpath(path, skill_root).replace("\\", "/")
|
||||
|
||||
@@ -385,6 +473,23 @@ class TestPolicyConfig003(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestPolicyConfig004(unittest.TestCase):
|
||||
def test_env_example_comments_are_user_facing(self) -> None:
|
||||
path = os.path.join(get_skill_root(), ".env.example")
|
||||
self.assertTrue(os.path.isfile(path), msg=".env.example missing")
|
||||
offenders = _config_004_comment_offenders(path)
|
||||
self.assertEqual(
|
||||
offenders,
|
||||
[],
|
||||
msg=_policy_msg(
|
||||
"POLICY-CONFIG-004",
|
||||
CONFIG_004_SOURCE,
|
||||
"\n".join(offenders)
|
||||
or "every active KEY in .env.example needs Chinese header+inline comments",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestPolicyTesting001(unittest.TestCase):
|
||||
def test_root_tests_do_not_hardcode_real_targets(self) -> None:
|
||||
skill_root = get_skill_root()
|
||||
|
||||
Reference in New Issue
Block a user