Enforce POLICY-RELEASE-001: align git tag with SKILL.md and SKILL_VERSION
All checks were successful
技能自动化发布 / release (push) Successful in 6s
All checks were successful
技能自动化发布 / release (push) Successful in 6s
This commit is contained in:
@@ -9,6 +9,11 @@
|
|||||||
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
|
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
|
||||||
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
|
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
|
||||||
|
|
||||||
|
## 1.0.104
|
||||||
|
|
||||||
|
- 新增发布契约 POLICY-RELEASE-001:`release.ps1` 打 tag 前强制校验 Next tag = `SKILL.md` version = `SKILL_VERSION`,避免市场一直显示「更新」
|
||||||
|
- 日常测试增加 `SKILL.md` 与 `SKILL_VERSION` 对齐;更新 DEVELOPMENT / POLICY_MATRIX / CHECKLIST A9-4
|
||||||
|
|
||||||
## 1.0.103
|
## 1.0.103
|
||||||
|
|
||||||
- 用户市场文档:说明/教程正文禁止与宿主页头、Tab 重复的文首一级标题;主分节统一用 `##`
|
- 用户市场文档:说明/教程正文禁止与宿主页头、Tab 重复的文首一级标题;主分节统一用 `##`
|
||||||
|
|||||||
2
SKILL.md
2
SKILL.md
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: 技能开发模板(通用业务版)
|
name: 技能开发模板(通用业务版)
|
||||||
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
|
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
|
||||||
version: 1.0.103
|
version: 1.0.104
|
||||||
author: 深圳匠厂科技有限公司
|
author: 深圳匠厂科技有限公司
|
||||||
metadata:
|
metadata:
|
||||||
openclaw:
|
openclaw:
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ checklist_version: 1.0
|
|||||||
- [ ] A9-1 **P0** 仓库默认测试套件通过(执行技能约定的一键测试命令,如 `python tests/run_tests.py -v`)
|
- [ ] A9-1 **P0** 仓库默认测试套件通过(执行技能约定的一键测试命令,如 `python tests/run_tests.py -v`)
|
||||||
- [ ] A9-2 **P0** 不以「仅模拟档通过」作为交活依据(有真实档要求时必须真实跑通或书面阶段性豁免)
|
- [ ] A9-2 **P0** 不以「仅模拟档通过」作为交活依据(有真实档要求时必须真实跑通或书面阶段性豁免)
|
||||||
- [ ] A9-3 **P0** **NEED_MANUAL** 入口定位表中定为「挂」的宿主入口(详情/Agent/工具栏/定时等)按声明抽测可通过;未在定位表中规划且未声明的入口可标 N/A(须引用定位表理由),**禁止**用「未声明」掩盖「从未做入口定位」
|
- [ ] A9-3 **P0** **NEED_MANUAL** 入口定位表中定为「挂」的宿主入口(详情/Agent/工具栏/定时等)按声明抽测可通过;未在定位表中规划且未声明的入口可标 N/A(须引用定位表理由),**禁止**用「未声明」掩盖「从未做入口定位」
|
||||||
- [ ] A9-4 **P1** 对外版本一致:详情、version、更新日志、发布标签能对上
|
- [ ] A9-4 **P0** 对外版本一致(POLICY-RELEASE-001):`SKILL.md` version = `SKILL_VERSION` = 发布 git tag(无前缀);`release.ps1` 打 tag 前会硬校验,漏改不得发布;CHANGELOG 有对应小节
|
||||||
- [ ] A9-5 **P1** 开发者按平台规则能安装并测到该技能(若平台有开发者可见性要求)
|
- [ ] A9-5 **P1** 开发者按平台规则能安装并测到该技能(若平台有开发者可见性要求)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -720,9 +720,15 @@ uses: client-jiangchang/jiangchang-platform-kit/.github/workflows/reusable-relea
|
|||||||
|
|
||||||
在执行 `release.ps1` 之前,必须先在本地确认 `python tests/run_tests.py -v` 通过。测试不通过不得发起正式发布。
|
在执行 `release.ps1` 之前,必须先在本地确认 `python tests/run_tests.py -v` 通过。测试不通过不得发起正式发布。
|
||||||
|
|
||||||
**发布前必须**将 `SKILL.md` 的 `version:` 与 `scripts/util/constants.py` 的 `SKILL_VERSION` 调整为将打出的版本(例如下一 patch),再执行 `release.ps1`。`release.ps1` **不会**自动改这两个字段;漏改会造成 git tag 与技能版本号不一致。
|
**POLICY-RELEASE-001(硬门禁)**:发布前必须将 `SKILL.md` 的 `version:`、`scripts/util/constants.py` 的 `SKILL_VERSION`、以及 `CHANGELOG.md` 对应小节,调整为**即将打出的**版本(与 `.\release.ps1 -DryRun` 打印的 `Next tag` 去掉 `v` 前缀一致)。`release.ps1` **不会**自动改这些字段;若三者与 Next tag 不一致,脚本在跑测试/打 tag **之前**直接失败,避免市场「最新版本」(git tag) 与安装包「当前版本」(本地 `SKILL.md`) 长期错位、技能市场一直显示「更新」。
|
||||||
|
|
||||||
`release.ps1` 会在 **auto commit 之前**、推送 tag 之前,对**当前工作区**自动执行默认测试(`python tests/run_tests.py -v`);失败则中止 release,不会留下失败状态的自动提交。仅紧急排障可使用 `-SkipTests` 跳过(会打印黄色警告)。`-DryRun` 预览发布时**仍会实际运行**默认测试,便于在不打 tag 的情况下验证门禁。
|
建议流程:
|
||||||
|
|
||||||
|
1. `.\release.ps1 -DryRun` 查看 `Next tag`(例如 `v1.0.104`)
|
||||||
|
2. 将 `SKILL.md` / `SKILL_VERSION` / `CHANGELOG` 改为 `1.0.104`
|
||||||
|
3. 再执行正式 `.\release.ps1`(可用 `-AutoCommit`)
|
||||||
|
|
||||||
|
`release.ps1` 会在 **auto commit 之前**、推送 tag 之前,对**当前工作区**自动执行默认测试(`python tests/run_tests.py -v`);失败则中止 release,不会留下失败状态的自动提交。仅紧急排障可使用 `-SkipTests` 跳过(会打印黄色警告)。`-DryRun` 预览发布时**仍会实际运行**版本对齐门禁与默认测试,便于在不打 tag 的情况下验证。
|
||||||
|
|
||||||
新增或调整开发规范时,须同步 [`development/POLICY_MATRIX.md`](POLICY_MATRIX.md) 与 [`tests/test_development_policy_guard.py`](../tests/test_development_policy_guard.py)(或注明由既有测试覆盖)。
|
新增或调整开发规范时,须同步 [`development/POLICY_MATRIX.md`](POLICY_MATRIX.md) 与 [`tests/test_development_policy_guard.py`](../tests/test_development_policy_guard.py)(或注明由既有测试覆盖)。
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
| POLICY-RPA-006 | 浏览器 RPA 必须保留 account-manager 生命周期标准;所有样例账号选择路径都须返回非空 `profile_dir + lease_token`,指定数字账号 ID 不得退化为无租约 `account get` | development/ACCOUNT_MANAGER.md;development/ADAPTER.md §兄弟依赖;development/RPA.md §0.2 | hard | 检查标准文档关键标记;扫描真实/仿真 `account_client.py` 的指定 ID 带租约实现并禁止空租约赋值 | `tests/test_development_policy_guard.py::TestPolicyRpa006` |
|
| POLICY-RPA-006 | 浏览器 RPA 必须保留 account-manager 生命周期标准;所有样例账号选择路径都须返回非空 `profile_dir + lease_token`,指定数字账号 ID 不得退化为无租约 `account get` | development/ACCOUNT_MANAGER.md;development/ADAPTER.md §兄弟依赖;development/RPA.md §0.2 | hard | 检查标准文档关键标记;扫描真实/仿真 `account_client.py` 的指定 ID 带租约实现并禁止空租约赋值 | `tests/test_development_policy_guard.py::TestPolicyRpa006` |
|
||||||
| POLICY-PACKAGING-001 | `scripts/**/*.py` 单文件 < 1000 行 | development/DEVELOPMENT.md §3.4 发布打包约束;development/RUNTIME.md §发布打包约束 | hard | 行数统计 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_scripts_py_files_under_pyarmor_line_limit` |
|
| POLICY-PACKAGING-001 | `scripts/**/*.py` 单文件 < 1000 行 | development/DEVELOPMENT.md §3.4 发布打包约束;development/RUNTIME.md §发布打包约束 | hard | 行数统计 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_scripts_py_files_under_pyarmor_line_limit` |
|
||||||
| POLICY-PACKAGING-002 | 文本文件 UTF-8 without BOM,无 `U+FEFF` | development/DEVELOPMENT.md §3.4;development/RUNTIME.md §编码与输出 | hard | BOM / 解码检查 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_text_files_are_utf8_without_bom` |
|
| POLICY-PACKAGING-002 | 文本文件 UTF-8 without BOM,无 `U+FEFF` | development/DEVELOPMENT.md §3.4;development/RUNTIME.md §编码与输出 | hard | BOM / 解码检查 | **已有测试覆盖**:`tests/test_release_packaging_constraints.py::test_text_files_are_utf8_without_bom` |
|
||||||
|
| POLICY-RELEASE-001 | 发布 tag(去掉前缀)必须等于 `SKILL.md` 的 `version:` 且等于 `scripts/util/constants.py` 的 `SKILL_VERSION`;`release.ps1` 打 tag 前硬失败,不自动改写字段 | development/DEVELOPMENT.md §15 发布;宿主市场「最新版本」来自 tag、「当前版本」来自包内 `SKILL.md` | hard | `release.ps1` 门禁 + 日常版本字段对齐测试 | `release.ps1`(`Assert-SkillReleaseVersionAlignment`);`tests/test_development_policy_guard.py::TestPolicyRelease001`;`tests/test_skill_metadata.py::test_skill_version_alignment` |
|
||||||
| POLICY-DOCS-001 | 本矩阵存在且包含上述全部 `policy_id` | 本次规范化约定 | hard | 解析 `development/POLICY_MATRIX.md` | `tests/test_development_policy_guard.py::TestPolicyDocs001` |
|
| POLICY-DOCS-001 | 本矩阵存在且包含上述全部 `policy_id` | 本次规范化约定 | hard | 解析 `development/POLICY_MATRIX.md` | `tests/test_development_policy_guard.py::TestPolicyDocs001` |
|
||||||
| POLICY-LOGGING-001 | CLI 入口必须调用 `setup_skill_logging` | development/LOGGING.md;development/RUNTIME.md | hard | 扫描 `scripts/cli/app.py` 含 `setup_skill_logging` 与 logger info | `tests/test_development_policy_guard.py::TestPolicyLogging001` |
|
| POLICY-LOGGING-001 | CLI 入口必须调用 `setup_skill_logging` | development/LOGGING.md;development/RUNTIME.md | hard | 扫描 `scripts/cli/app.py` 含 `setup_skill_logging` 与 logger info | `tests/test_development_policy_guard.py::TestPolicyLogging001` |
|
||||||
| POLICY-LOGGING-002 | service 主任务入口必须使用 `get_skill_logger` | development/LOGGING.md;development/DEVELOPMENT.md | hard | 扫描 `scripts/service/task_service.py` | `tests/test_development_policy_guard.py::TestPolicyLogging002` |
|
| POLICY-LOGGING-002 | service 主任务入口必须使用 `get_skill_logger` | development/LOGGING.md;development/DEVELOPMENT.md | hard | 扫描 `scripts/service/task_service.py` | `tests/test_development_policy_guard.py::TestPolicyLogging002` |
|
||||||
|
|||||||
108
release.ps1
108
release.ps1
@@ -7,6 +7,10 @@
|
|||||||
Encryption, ZIP packaging, and marketplace sync are done by CI
|
Encryption, ZIP packaging, and marketplace sync are done by CI
|
||||||
(.github/workflows/release_skill.yaml → reusable-release-skill.yaml@main).
|
(.github/workflows/release_skill.yaml → reusable-release-skill.yaml@main).
|
||||||
|
|
||||||
|
POLICY-RELEASE-001: before tagging, requires SKILL.md version and
|
||||||
|
scripts/util/constants.py SKILL_VERSION to equal the next tag (without prefix).
|
||||||
|
release.ps1 does not auto-edit those fields.
|
||||||
|
|
||||||
Requires: git, PowerShell 5+
|
Requires: git, PowerShell 5+
|
||||||
|
|
||||||
After fetch, runs default tests against the current working tree (before auto-commit):
|
After fetch, runs default tests against the current working tree (before auto-commit):
|
||||||
@@ -162,6 +166,93 @@ function Get-NextTag {
|
|||||||
return "$TagPrefix$($latest.Major).$($latest.Minor).$([int]$latest.Patch + 1)"
|
return "$TagPrefix$($latest.Major).$($latest.Minor).$([int]$latest.Patch + 1)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Read-SkillMdVersion {
|
||||||
|
param([Parameter(Mandatory = $true)][string]$SkillRoot)
|
||||||
|
|
||||||
|
$path = Join-Path $SkillRoot "SKILL.md"
|
||||||
|
if (-not (Test-Path -LiteralPath $path)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$text = Get-Content -LiteralPath $path -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||||
|
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$m = [regex]::Match($text, '(?m)^version:\s*["'']?([0-9]+\.[0-9]+\.[0-9]+)["'']?\s*$')
|
||||||
|
if (-not $m.Success) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
return $m.Groups[1].Value.Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Read-SkillConstantsVersion {
|
||||||
|
param([Parameter(Mandatory = $true)][string]$SkillRoot)
|
||||||
|
|
||||||
|
$path = Join-Path $SkillRoot "scripts\util\constants.py"
|
||||||
|
if (-not (Test-Path -LiteralPath $path)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$text = Get-Content -LiteralPath $path -Raw -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||||
|
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
$m = [regex]::Match($text, '(?m)^SKILL_VERSION\s*=\s*["'']([0-9]+\.[0-9]+\.[0-9]+)["'']')
|
||||||
|
if (-not $m.Success) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
return $m.Groups[1].Value.Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
# POLICY-RELEASE-001: marketplace latest (git tag) must equal package manifest versions.
|
||||||
|
function Assert-SkillReleaseVersionAlignment {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$SkillRoot,
|
||||||
|
[Parameter(Mandatory = $true)][string]$NextTag,
|
||||||
|
[Parameter(Mandatory = $true)][string]$TagPrefix
|
||||||
|
)
|
||||||
|
|
||||||
|
$skillMdPath = Join-Path $SkillRoot "SKILL.md"
|
||||||
|
if (-not (Test-Path -LiteralPath $skillMdPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected = $NextTag
|
||||||
|
if ($TagPrefix -and $expected.StartsWith($TagPrefix)) {
|
||||||
|
$expected = $expected.Substring($TagPrefix.Length)
|
||||||
|
}
|
||||||
|
$expected = $expected.Trim()
|
||||||
|
if ($expected -notmatch '^\d+\.\d+\.\d+$') {
|
||||||
|
throw "Release check failed (POLICY-RELEASE-001): cannot derive semver from Next tag '$NextTag'."
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdVersion = Read-SkillMdVersion -SkillRoot $SkillRoot
|
||||||
|
$constVersion = Read-SkillConstantsVersion -SkillRoot $SkillRoot
|
||||||
|
|
||||||
|
$problems = @()
|
||||||
|
if ([string]::IsNullOrWhiteSpace($mdVersion)) {
|
||||||
|
$problems += "SKILL.md missing top-level version: X.Y.Z"
|
||||||
|
}
|
||||||
|
elseif ($mdVersion -ne $expected) {
|
||||||
|
$problems += "SKILL.md version=$mdVersion (want $expected)"
|
||||||
|
}
|
||||||
|
if ([string]::IsNullOrWhiteSpace($constVersion)) {
|
||||||
|
$problems += "scripts/util/constants.py missing SKILL_VERSION = `"X.Y.Z`""
|
||||||
|
}
|
||||||
|
elseif ($constVersion -ne $expected) {
|
||||||
|
$problems += "SKILL_VERSION=$constVersion (want $expected)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($problems.Count -gt 0) {
|
||||||
|
throw (
|
||||||
|
"Release aborted (POLICY-RELEASE-001): Next tag=$NextTag requires manifest version $expected, but " +
|
||||||
|
($problems -join "; ") +
|
||||||
|
". Bump SKILL.md version and scripts/util/constants.py SKILL_VERSION to $expected (and CHANGELOG), then re-run release.ps1. " +
|
||||||
|
"release.ps1 does not auto-edit these fields."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Version alignment OK: tag=$NextTag == SKILL.md=$mdVersion == SKILL_VERSION=$constVersion" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
function Assert-SkillReleasePackagingSources {
|
function Assert-SkillReleasePackagingSources {
|
||||||
param([Parameter(Mandatory = $true)][string]$SkillRoot)
|
param([Parameter(Mandatory = $true)][string]$SkillRoot)
|
||||||
|
|
||||||
@@ -283,6 +374,15 @@ try {
|
|||||||
Assert-SkillReleasePackagingSources -SkillRoot $skillRoot
|
Assert-SkillReleasePackagingSources -SkillRoot $skillRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$nextTag = Get-NextTag -TagPrefix $Prefix
|
||||||
|
Write-Host "Next tag: $nextTag" -ForegroundColor Green
|
||||||
|
Assert-SkillReleaseVersionAlignment -SkillRoot $skillRoot -NextTag $nextTag -TagPrefix $Prefix
|
||||||
|
|
||||||
|
$existing = @(Get-GitOutput -GitArgs @("tag", "--list", $nextTag))
|
||||||
|
if ($existing.Length -gt 0) {
|
||||||
|
throw "Tag already exists: $nextTag"
|
||||||
|
}
|
||||||
|
|
||||||
Invoke-DefaultTests -SkillRoot $skillRoot -Skip:$SkipTests
|
Invoke-DefaultTests -SkillRoot $skillRoot -Skip:$SkipTests
|
||||||
|
|
||||||
Ensure-CleanOrAutoCommit -DoAutoCommit:$AutoCommit -NeedClean:$RequireClean -IsDryRun:$DryRun -Msg $CommitMessage
|
Ensure-CleanOrAutoCommit -DoAutoCommit:$AutoCommit -NeedClean:$RequireClean -IsDryRun:$DryRun -Msg $CommitMessage
|
||||||
@@ -304,14 +404,6 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$nextTag = Get-NextTag -TagPrefix $Prefix
|
|
||||||
Write-Host "Next tag: $nextTag" -ForegroundColor Green
|
|
||||||
|
|
||||||
$existing = @(Get-GitOutput -GitArgs @("tag", "--list", $nextTag))
|
|
||||||
if ($existing.Length -gt 0) {
|
|
||||||
throw "Tag already exists: $nextTag"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($DryRun) {
|
if ($DryRun) {
|
||||||
Write-Host "[DryRun] Would run: git tag -a $nextTag -F <utf8-msg-file> ($Message)" -ForegroundColor Yellow
|
Write-Host "[DryRun] Would run: git tag -a $nextTag -F <utf8-msg-file> ($Message)" -ForegroundColor Yellow
|
||||||
Write-Host "[DryRun] Would run: git push origin $nextTag" -ForegroundColor Yellow
|
Write-Host "[DryRun] Would run: git push origin $nextTag" -ForegroundColor Yellow
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
|
"""技能标识、版本与平台公共库约束(复制后请修改 slug/version/logger)。"""
|
||||||
|
|
||||||
SKILL_SLUG = "your-skill-slug"
|
SKILL_SLUG = "your-skill-slug"
|
||||||
SKILL_VERSION = "1.0.103"
|
SKILL_VERSION = "1.0.104"
|
||||||
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
|
LOG_LOGGER_NAME = "openclaw.skill.your_skill_slug"
|
||||||
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from jiangchang_skill_core import config
|
|||||||
|
|
||||||
# scripts/ 已由 _support 注入 sys.path
|
# scripts/ 已由 _support 注入 sys.path
|
||||||
from cli.app import main
|
from cli.app import main
|
||||||
from util.constants import SKILL_SLUG
|
from util.constants import SKILL_SLUG, SKILL_VERSION
|
||||||
|
|
||||||
|
|
||||||
class TestCliSmoke(unittest.TestCase):
|
class TestCliSmoke(unittest.TestCase):
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ POLICY_IDS = (
|
|||||||
"POLICY-RPA-006",
|
"POLICY-RPA-006",
|
||||||
"POLICY-PACKAGING-001",
|
"POLICY-PACKAGING-001",
|
||||||
"POLICY-PACKAGING-002",
|
"POLICY-PACKAGING-002",
|
||||||
|
"POLICY-RELEASE-001",
|
||||||
"POLICY-DOCS-001",
|
"POLICY-DOCS-001",
|
||||||
"POLICY-LOGGING-001",
|
"POLICY-LOGGING-001",
|
||||||
"POLICY-LOGGING-002",
|
"POLICY-LOGGING-002",
|
||||||
@@ -1530,6 +1531,44 @@ class TestPolicyDataSync001(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPolicyRelease001(unittest.TestCase):
|
||||||
|
def test_release_ps1_enforces_version_alignment(self) -> None:
|
||||||
|
skill_root = get_skill_root()
|
||||||
|
path = os.path.join(skill_root, "release.ps1")
|
||||||
|
self.assertTrue(
|
||||||
|
os.path.isfile(path),
|
||||||
|
msg=_policy_msg("POLICY-RELEASE-001", "release.ps1", "file missing"),
|
||||||
|
)
|
||||||
|
text = _read_text(path)
|
||||||
|
self.assertIn(
|
||||||
|
"Assert-SkillReleaseVersionAlignment",
|
||||||
|
text,
|
||||||
|
msg=_policy_msg(
|
||||||
|
"POLICY-RELEASE-001",
|
||||||
|
"release.ps1",
|
||||||
|
"must define Assert-SkillReleaseVersionAlignment",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"POLICY-RELEASE-001",
|
||||||
|
text,
|
||||||
|
msg=_policy_msg(
|
||||||
|
"POLICY-RELEASE-001",
|
||||||
|
"release.ps1",
|
||||||
|
"must reference POLICY-RELEASE-001",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertRegex(
|
||||||
|
text,
|
||||||
|
r"Assert-SkillReleaseVersionAlignment\s+-SkillRoot",
|
||||||
|
msg=_policy_msg(
|
||||||
|
"POLICY-RELEASE-001",
|
||||||
|
"release.ps1",
|
||||||
|
"must invoke Assert-SkillReleaseVersionAlignment before tagging",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestPolicyDocs001(unittest.TestCase):
|
class TestPolicyDocs001(unittest.TestCase):
|
||||||
def test_policy_matrix_exists_and_lists_policy_ids(self) -> None:
|
def test_policy_matrix_exists_and_lists_policy_ids(self) -> None:
|
||||||
skill_root = get_skill_root()
|
skill_root = get_skill_root()
|
||||||
|
|||||||
@@ -50,6 +50,26 @@ class TestSkillMetadata(unittest.TestCase):
|
|||||||
self.assertEqual(SKILL_SLUG, skill_dir)
|
self.assertEqual(SKILL_SLUG, skill_dir)
|
||||||
self.assertEqual(slug_md, skill_dir)
|
self.assertEqual(slug_md, skill_dir)
|
||||||
|
|
||||||
|
def test_skill_version_alignment(self) -> None:
|
||||||
|
"""SKILL.md version 必须与 constants.SKILL_VERSION 一致(POLICY-RELEASE-001 日常侧)。"""
|
||||||
|
root = get_skill_root()
|
||||||
|
md_path = os.path.join(root, "SKILL.md")
|
||||||
|
with open(md_path, encoding="utf-8") as f:
|
||||||
|
md = f.read()
|
||||||
|
match = re.search(r"(?m)^version:\s*[\"']?([0-9]+\.[0-9]+\.[0-9]+)[\"']?\s*$", md)
|
||||||
|
self.assertIsNotNone(match, "SKILL.md must declare top-level semver version")
|
||||||
|
assert match is not None
|
||||||
|
md_version = match.group(1)
|
||||||
|
|
||||||
|
from util.constants import SKILL_VERSION
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
md_version,
|
||||||
|
SKILL_VERSION,
|
||||||
|
"SKILL.md version 必须与 constants.SKILL_VERSION 一致(发布前再与 git tag 对齐)",
|
||||||
|
)
|
||||||
|
self.assertRegex(SKILL_VERSION, r"^\d+\.\d+\.\d+$")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user