diff --git a/CHANGELOG.md b/CHANGELOG.md index 02987f0..5baa4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ - 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节 - 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段 +## 1.0.112 + +- 新技能的名字不必再以平台或 `default` 结尾。没有抖音、小红书、亚马逊这类外部平台时,用动词加名词即可 +- 不要再把 `default` 当作占位平台。已经发布、名字末尾是 `default` 的技能不用改名 + ## 1.0.111 - 沉淀数据管理呈现加法:同一 `cellPresentation.enabled` 支持单元格、整行、整列、行列交叉区域(闭集 tone) diff --git a/SKILL.md b/SKILL.md index 211919b..67417ad 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 技能开发模板(通用业务版) description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。" -version: 1.0.111 +version: 1.0.112 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -159,7 +159,7 @@ python {baseDir}/scripts/main.py init-db ## 技能命名与 slug -- 标准格式:`{verb}-{noun-phrase}-{platform}`(详见 `development/NAMING.md`)。 +- 标准格式:`{verb}-{noun-phrase}`,绑定外部平台时才加 `-{platform}`(详见 `development/NAMING.md`)。不要用 `default` 占位。 - slug / repo / 目录 / `SKILL_SLUG` / DB 文件名:**英文** kebab-case;`name`、`description`、根 `README.md`:**中文**。 - 复制为新业务技能前**必须**阅读 `development/NAMING.md`。 - `name`(frontmatter):**用户可见的中文业务名称**;`description` 是说明,不是名称。 diff --git a/assets/naming/scopes.txt b/assets/naming/scopes.txt index 3c41cf5..209e082 100644 --- a/assets/naming/scopes.txt +++ b/assets/naming/scopes.txt @@ -1,4 +1,3 @@ all batch multi -default diff --git a/development/DEVELOPMENT.md b/development/DEVELOPMENT.md index b0e4179..0ef6e45 100644 --- a/development/DEVELOPMENT.md +++ b/development/DEVELOPMENT.md @@ -925,7 +925,7 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea 每个新 skill 发布前,建议技术人员逐条确认: - [ ] 目录结构符合当前模板 -- [ ] slug 符合 [`NAMING.md`](NAMING.md)(verb-noun-platform) +- [ ] slug 符合 [`NAMING.md`](NAMING.md)(动词-名词,平台可选;不要用 `default` 占位) - [ ] 目录名、`SKILL.md` slug、`constants.SKILL_SLUG` 三者一致 - [ ] `SKILL.md` 中 slug、名称、描述都已替换 - [ ] `SKILL.md` 的 `developer_ids` 已换成**本人**匠厂用户 ID(设置 → 用户信息 → 复制),不是模板示例 ID diff --git a/development/NAMING.md b/development/NAMING.md index d057f68..1358ffe 100644 --- a/development/NAMING.md +++ b/development/NAMING.md @@ -13,23 +13,24 @@ | ❌ **不修改** | 其他仓库(尤其 `account-manager`)不在本文约束范围内 | | ✅ **模板占位** | 模板本体的 `your-skill-slug` 占位必须继续通过全部测试 | -**新技能勿仿**历史 2 段无平台形态(如 `reconcile-finance`)或不符合 verb-noun-platform 的旧 repo 名;校验器对历史 repo **不追溯**,但规范中明确标注为反模式。 +**新技能**使用「动词-名词」,外部平台可选。不要再为了凑段数追加 `default`。校验器不追溯已上线仓库;已经发布、末尾是 `default` 的技能不用改名。 --- ## 2. 命名格式 -标准形态(约 95% 新技能): +标准形态: ```text -{verb}-{noun-phrase}-{platform} +{verb}-{noun-phrase} +{verb}-{noun-phrase}-{platform} # 仅当技能绑定外部平台时 ``` | 部分 | 规则 | |------|------| | **verb** | 1 个词,来自 [动词白名单](#5-动词白名单) | | **noun-phrase** | 1~3 个词(按 `-` 分段),描述业务对象 | -| **platform** | 1 个词,来自 [平台白名单](#6-平台白名单);可含连字符(如 `single-window`) | +| **platform** | **可选**。没有抖音、小红书、亚马逊这类外部系统时省略。有平台时 1 个词,来自 [平台白名单](#6-平台白名单);可含连字符(如 `single-window`) | **展示层分工不变:** @@ -44,8 +45,8 @@ | 约束 | 值 | |------|-----| -| 格式 | `{verb}-{noun-phrase}-{platform}`(标准型,默认) | -| 总段数(按 `-` 分割) | **3~5 段**(标准型);scope 型见 [§4](#4-三类合法形态) | +| 格式 | `{verb}-{noun-phrase}`;有外部平台时才加 `-{platform}` | +| 总段数(按 `-` 分割) | **2~5 段** | | 总长度 | **≤ 48 字符** | | 字符集 | 小写字母、数字、连字符;kebab-case | | 禁止 | 中文 slug、下划线、**平台放最前**(如 `amazon-download-xxx`) | @@ -56,12 +57,26 @@ ## 4. 三类合法形态 -### 4.1 标准型(95%) +### 4.1 无平台(常见) -`verb-noun-platform` 或 `verb-noun-noun-platform`(3~5 段) +`verb-noun` 或 `verb-noun-noun`(2~4 段) - 第 1 段 ∈ 动词白名单 -- 最后 1 段(或 hyphenated 平台的最后 2 段)∈ 平台白名单 +- 其余为 noun-phrase(1~3 段) +- **不要**追加 `default` + +示例: + +- `generate-monthly-statements` +- `reconcile-finance` +- `process-article` + +### 4.2 标准型(绑定外部平台) + +`verb-noun-platform`(在无平台形态后加平台,总段数仍 ≤ 5) + +- 第 1 段 ∈ 动词白名单 +- 最后 1 段(或 hyphenated 平台的最后若干段)∈ 平台白名单 - 中间为 noun-phrase(1~3 段) 示例: @@ -71,19 +86,19 @@ - `enter-sales-receipt-kingdee` - `fill-export-declaration-single-window` -### 4.2 scope 型(少见) +### 4.3 scope 型(少见) `verb-noun-scope` -- 最后 1 段 ∈ scope 白名单:`all` | `batch` | `multi` | `default` +- 最后 1 段 ∈ scope 白名单:`all` | `batch` | `multi` - 第 1 段 ∈ 动词白名单 -- 总段数 3~5 +- 至少还有一段名词(因此最少 3 段) 示例:`generate-article-all` -> 说明:`geo-generate-article-all` 若作为新技能且不在豁免表、首段 `geo` 不在动词表,应写为 `generate-article-all`。 +> `default` 已从 scope 词表移除。它不是平台,也不表示范围。新技能不要写 `generate-article-default` 这类占位。已发布且末尾为 `default` 的仓库保持原 slug;同步本校验后只会警告,不会因此失败。 -### 4.3 历史豁免(不校验语义) +### 4.4 历史豁免(不校验语义) 以下 slug 跳过 verb/platform 语义校验(见 `LEGACY_EXEMPT_SLUGS`): @@ -121,7 +136,7 @@ | lianlian | single-window | export-rebate | | etax | | | -scope 词表:[`assets/naming/scopes.txt`](../assets/naming/scopes.txt) — `all`、`batch`、`multi`、`default` +scope 词表:[`assets/naming/scopes.txt`](../assets/naming/scopes.txt) — `all`、`batch`、`multi`。不含 `default`。 --- @@ -164,7 +179,7 @@ slug **末段 platform** 与 `account-manager` 中的 `platform_key` **语义对 ## 9. 复制新技能流程 -1. **先定 slug** — 按本文规范拟定 `{verb}-{noun-phrase}-{platform}`,确认在白名单内且 ≤ 48 字符 +1. **先定 slug** — `{verb}-{noun-phrase}`,仅在绑定外部平台时加 `-{platform}`。确认动词/平台在白名单内且 ≤ 48 字符。不要用 `default` 占位。 2. **复制目录** — **推荐** [`tools/scaffold_skill.ps1`](../tools/scaffold_skill.ps1)(`-Slug` 与目录名一致);勿整包复制带走 `.git` 2.5. **清理 Git(若未用 scaffold)** — 删除 `.git`、`.openclaw-skill-template`、`.pytest_cache`;再 `git init` 与 `git remote add origin` 3. **全局替换** — `your-skill-slug` → 新 slug;更新 `SKILL.md`、`constants.py`、文档中的占位 @@ -179,10 +194,12 @@ slug **末段 platform** 与 `account-manager` 中的 `platform_key` **语义对 | slug | 说明 | |------|------| -| `download-settlement-report-amazon` | 标准型,4 段 | -| `add-product-shopee` | 标准型,3 段 | -| `enter-sales-receipt-kingdee` | 标准型 | -| `fill-export-declaration-single-window` | 标准型,平台含连字符 | +| `generate-monthly-statements` | 无平台 | +| `reconcile-finance` | 无平台,动词 + 一个名词 | +| `download-settlement-report-amazon` | 有平台 | +| `add-product-shopee` | 有平台 | +| `enter-sales-receipt-kingdee` | 有平台 | +| `fill-export-declaration-single-window` | 有平台,平台含连字符 | | `generate-article-all` | scope 型 | ### ❌ 反例 @@ -191,7 +208,7 @@ slug **末段 platform** 与 `account-manager` 中的 `platform_key` **语义对 |------|------| | `amazon-download-settlement-report` | 平台在最前 | | `enter-cross-border-sales-receipt-voucher-kingdee` | 过长、noun 臃肿 | -| `reconcile-finance` | 新技能禁止 2 段无平台 | +| `generate-article-default` | `default` 不是平台;应写成 `generate-article` | | `download_settlement_amazon` | 非 kebab-case(下划线) | --- @@ -201,14 +218,14 @@ slug **末段 platform** 与 `account-manager` 中的 `platform_key` **语义对 - **平台前置**:`amazon-download-...`、`shopee-add-...` - **英文占位 name**:复制后仍保留 `My Skill` 或纯英文 slug 风格名称 - **只改目录名**:`SKILL.md` slug 与 `constants.SKILL_SLUG` 未同步 -- **noun 堆砌**:中间段过多导致超 48 字符或难以阅读 -- **仿历史 2 段 repo**:`reconcile-finance`、`content-manager` 等 +- **用 default 占位**:`generate-article-default`。没有外部平台就省略末段 +- **名词堆砌**:中间段过多导致超 48 字符或难以阅读 --- ## 12. 演进说明(当前不启用) -当技能库规模 **> 200** 时,可再评估引入 `{domain}-verb-noun-platform` 前缀形态。**当前不启用**,新技能一律使用本文 verb-noun-platform 标准型或 scope 型。 +当技能库规模 **> 200** 时,可再评估引入 `{domain}-verb-noun-platform` 前缀形态。**当前不启用**。新技能使用动词-名词;只有绑定外部平台时才使用 verb-noun-platform,或使用 scope 型。 --- @@ -218,9 +235,9 @@ slug **末段 platform** 与 `account-manager` 中的 `platform_key` **语义对 | 历史 slug | 说明 | |-----------|------| -| `account-manager` | 跨平台账号管理,非 verb-noun-platform | -| `reconcile-finance` | 2 段无 platform | -| `scrape-contacts-lianjia` | 平台名不在当前白名单(链家) | +| `account-manager` | 跨平台账号管理,非动词-名词 | +| `reconcile-finance` | 现已是合法的无平台形态;旧文档曾把它标成反例 | +| `scrape-contacts-lianjia` | 平台名不在当前白名单(链家);新技能若要表达链家,先把平台加入白名单 | | `skill-template` | 模板仓库本身 | 校验器通过 `LEGACY_EXEMPT_SLUGS` 对模板占位与少数豁免 slug 跳过语义检查;**新复制出去的业务 skill 不在豁免表内**,必须满足 §3 硬约束。 diff --git a/development/POLICY_MATRIX.md b/development/POLICY_MATRIX.md index bd5ac4e..c5def2a 100644 --- a/development/POLICY_MATRIX.md +++ b/development/POLICY_MATRIX.md @@ -60,7 +60,7 @@ | 是否满足完整快照语义 / 空结果保护实现 | references/SCHEMA.md | 见 `test_sync_contract.py.sample` | | 是否把系统导出与业务同步混淆 | references/ACTIONS.md;SCHEMA.md | 文档 + sample;勿做源码关键词误报扫描 | | `db/` 层不得调浏览器或拼业务流程 | development/DEVELOPMENT.md §11 | 需跨文件调用图 | -| slug 命名语义(verb-noun-platform) | development/NAMING.md | 部分覆盖于 `tests/test_slug_naming.py`,非本矩阵 hard policy | +| slug 命名语义(动词-名词,平台可选) | development/NAMING.md | 部分覆盖于 `tests/test_slug_naming.py`,非本矩阵 hard policy | | 复制模板不得保留 `.git` / template marker | development/DEVELOPMENT.md §4 | 由 `tests/test_scaffold_guard.py` 守护,属脚手架场景 | | 共享 runtime 导入来源非技能目录 | development/RUNTIME.md | 运行时依赖已安装包,见 `tests/test_platform_import.py` | | RPA 真实轨迹、选择器纪律、状态等待、HITL 超时与有限重试是否正确实现 | development/AI_RPA.md;development/RPA.md §0–§1 | 文档和默认节奏已守护,真实行为与 DOM 质量仍需人工验收 | diff --git a/development/TESTING.md b/development/TESTING.md index 1b79a74..5afaebe 100644 --- a/development/TESTING.md +++ b/development/TESTING.md @@ -34,7 +34,7 @@ - **adapter profile**:[`tests/test_adapter_profile_policy.py`](../tests/test_adapter_profile_policy.py) + [`adapter_test_utils`](../tests/adapter_test_utils.py) ——验证在未授权情况下绝不误判开启真实网络/RPA。 - **发布打包守护**:[`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 命名规范校验。 +- **slug 语义**:[`tests/test_slug_naming.py`](../tests/test_slug_naming.py) ——动词-名词命名;平台可选,`default` 不再作为占位。 - **HTML 报告壳**:[`tests/test_html_page_service.py`](../tests/test_html_page_service.py) 守护 `assets/html/example-report.html` 的数据管理 iframe 预览布局基线,并守护 ACTIONS.md 里的壳子页目录 `assets/html/pages` 与 `jiangchang.call`。业务技能若有自定义 `assets/html/*.html` 或 `pages/*.html`,建议在自有仓库加同类静态断言(见 `references/ACTIONS.md`「宿主预览布局」)。不要把 `example-report.html` 放进 `pages/`。 **原则:`tests/test_*.py` 不允许隐形访问外网、不允许拉起真实浏览器、不允许读写开发者机器的真实数据根**。如果需要仿真服务器,也应仅在 integration(并由档位变量放行)。 diff --git a/scripts/util/constants.py b/scripts/util/constants.py index 5e64c79..5427b29 100644 --- a/scripts/util/constants.py +++ b/scripts/util/constants.py @@ -1,6 +1,6 @@ """技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。""" SKILL_SLUG = "your-skill-slug" -SKILL_VERSION = "1.0.111" +SKILL_VERSION = "1.0.112" LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug" PLATFORM_KIT_MIN_VERSION = "1.2.11" diff --git a/scripts/util/slug_naming.py b/scripts/util/slug_naming.py index 0dda7cd..563be46 100644 --- a/scripts/util/slug_naming.py +++ b/scripts/util/slug_naming.py @@ -7,7 +7,7 @@ import re _KEBAB_SLUG = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") _MAX_SLUG_LENGTH = 48 -_MIN_SEGMENTS = 3 +_MIN_SEGMENTS = 2 _MAX_SEGMENTS = 5 _MAX_NOUN_SEGMENTS = 3 @@ -60,7 +60,9 @@ _BUILTIN_PLATFORMS = frozenset( "etax", } ) -_BUILTIN_SCOPES = frozenset({"all", "batch", "multi", "default"}) +_BUILTIN_SCOPES = frozenset({"all", "batch", "multi"}) +# 旧技能用 default 占位末段。不再当成合法 scope;校验只警告,避免同步后挡住已发布仓库。 +_DEPRECATED_PLATFORM_PLACEHOLDER = "default" LEGACY_EXEMPT_SLUGS: frozenset[str] = frozenset( { @@ -117,11 +119,12 @@ def _match_platform_suffix( def classify_slug_form(segments: list[str], *, slug: str = "") -> str: """ - 返回: 'legacy_exempt' | 'standard' | 'scope' | 'invalid' + 返回: 'legacy_exempt' | 'standard' | 'scope' | 'plain' | 'invalid' - legacy_exempt: slug in LEGACY_EXEMPT_SLUGS - - standard: 末段匹配 platforms, 首段 in verbs, 3<=len<=5 - - scope: 末段 in scopes, 首段 in verbs, 3<=len<=5 + - standard: 末段匹配 platforms,首段 in verbs,2<=len<=5,且动词后至少还有一段名词 + - scope: 末段 in scopes(all/batch/multi),首段 in verbs,至少 verb-noun-scope + - plain: 动词 + 名词,无平台、无 scope - invalid: 其他 """ if slug in LEGACY_EXEMPT_SLUGS: @@ -138,12 +141,14 @@ def classify_slug_form(segments: list[str], *, slug: str = "") -> str: return "invalid" if segments[-1] in scopes: + if len(segments) < 3: + return "invalid" return "scope" if _match_platform_suffix(segments, platforms) is not None: return "standard" - return "invalid" + return "plain" def validate_slug_semantics( @@ -173,8 +178,8 @@ def validate_slug_semantics( if segment_count < _MIN_SEGMENTS: msg = ( - f"slug 段数应为 {_MIN_SEGMENTS}~{_MAX_SEGMENTS}(verb-noun-platform)," - f"当前为 {segment_count} 段:{slug!r};新技能禁止 2 段无平台形态(如 reconcile-finance)" + f"slug 段数应为 {_MIN_SEGMENTS}~{_MAX_SEGMENTS}(动词-名词,平台可选)," + f"当前为 {segment_count} 段:{slug!r}" ) (errors if strict else warnings).append(msg) return errors, warnings @@ -197,9 +202,6 @@ def validate_slug_semantics( form = classify_slug_form(segments, slug=slug) if form == "scope": - if segments[-1] not in scopes: - msg = f"slug 末段应为 scope 白名单中的词:{segments[-1]!r}(slug={slug!r})" - (errors if strict else warnings).append(msg) noun_count = segment_count - 2 elif form == "standard": platform_match = _match_platform_suffix(segments, platforms) @@ -210,13 +212,17 @@ def validate_slug_semantics( else: _platform_name, platform_part_count = platform_match noun_count = segment_count - 1 - platform_part_count + elif form == "plain": + noun_count = segment_count - 1 + if segments[-1] == _DEPRECATED_PLATFORM_PLACEHOLDER: + warnings.append( + "slug 末段 default 已不再作为平台占位;没有外部平台时直接省略。" + f"已发布的技能不用改名:{slug!r}" + ) else: - if segment_count >= _MIN_SEGMENTS: - if segments[-1] not in scopes: - platform_match = _match_platform_suffix(segments, platforms) - if platform_match is None: - msg = f"slug 末段应匹配平台或 scope 白名单:{slug!r}" - (errors if strict else warnings).append(msg) + if segments and segments[-1] in scopes and segment_count < 3: + msg = f"scope 型 slug 至少为动词-名词-scope:{slug!r}" + (errors if strict else warnings).append(msg) noun_count = 0 if noun_count > _MAX_NOUN_SEGMENTS: diff --git a/tests/test_slug_naming.py b/tests/test_slug_naming.py index 791d177..ae25b58 100644 --- a/tests/test_slug_naming.py +++ b/tests/test_slug_naming.py @@ -44,6 +44,8 @@ class TestSlugNaming(unittest.TestCase): for slug in ( "download-settlement-report-amazon", "add-product-shopee", + "generate-monthly-statements", + "reconcile-finance", "generate-article-all", "your-skill-slug", "account-manager", @@ -56,8 +58,7 @@ class TestSlugNaming(unittest.TestCase): cases = ( "amazon-download-settlement-report", "enter-cross-border-sales-receipt-voucher-kingdee", - "reconcile-finance", - "download-settlement-report-unknownplat", + "generate", "foobar-settlement-report-amazon", ) for slug in cases: @@ -78,6 +79,19 @@ class TestSlugNaming(unittest.TestCase): self.assertTrue(errors) self.assertTrue(any("48" in err for err in errors)) + def test_plain_slug_without_platform(self) -> None: + slug = "generate-monthly-statements" + self.assertEqual(classify_slug_form(parse_slug_segments(slug), slug=slug), "plain") + errors, warnings = validate_slug_semantics(slug) + self.assertEqual(errors, [], msg=f"errors={errors!r} warnings={warnings!r}") + self.assertEqual(warnings, []) + + def test_default_suffix_warns_but_does_not_fail(self) -> None: + slug = "generate-article-default" + errors, warnings = validate_slug_semantics(slug) + self.assertEqual(errors, [], msg=f"errors={errors!r}") + self.assertTrue(any("default" in item for item in warnings), msg=f"warnings={warnings!r}") + def test_legacy_exempt_set(self) -> None: self.assertIn("your-skill-slug", LEGACY_EXEMPT_SLUGS) self.assertIn("account-manager", LEGACY_EXEMPT_SLUGS)