Files
knowledge-governance/scripts/version.py
T
姚章浩 5f3a71cade
build / build (push) Successful in 26s
feat(version): 版本号唯一来源 = CHANGELOG.md,并要求写清改了什么与怎么验收
背景:本仓此前没有任何版本/构建元信息 —— 没有 ldflags 注入、也没有产物可读的
版本号,"线上跑的是哪个提交"答不出来;"这次改了什么、怎么验收"也没有载体。

落地:
- CHANGELOG.md:**版本号的唯一来源**(正本 §6.1:版本号只准有一处)。刻意不放
  VERSION 文件、也不靠 git tag —— CI 是 `git fetch --depth 50`,取不到 tag,
  `git describe` 会静默退化。每个已发布版本必须有两节:### 改了什么 与 ### 怎么验收。
- scripts/version.py:从 CHANGELOG 顶部解析版本号;解析不到即非 0 退出(构建据此失败)。
- scripts/check_changelog.py:结构自检(两节齐、带日期、版本号不重复且严格递减、
  小节不为空),带 --self-test 反向自检。
- internal/version:三个构建期注入变量 + 契约测试(注入的号必须等于 CHANGELOG 顶部)。
- Makefile:build 带 -ldflags 注入;verify 增加 changelog-check 与 version-contract。
- Dockerfile:ARG VERSION/COMMIT_SHA/BUILD_TIME + ldflags(未透传时落 dev —— 故意显眼,
  好过看起来正常的假版本号)。
- CI:新增「版本与变更记录契约」步,并显式断言契约测试真的 PASS(被跳过的断言等于没有断言)。
- docs/提交规范.md(ci_gate.py 的报错文案一直在指它)、docs/agents/traps.md(7 条实测陷阱)。

验证(都真跑过):
- make version → 0.1.0;make changelog-check → 合规
- make version-contract → PASS;注入 9.9.9(CHANGELOG 里没有)→ FAIL(反向自检)
- 删掉真实 CHANGELOG 的「怎么验收」小节 → 报红(对真实文件做,不只是夹具)
- go build ./... 与 go vet ./internal/version/... 通过

未做(会与在审 PR #2 冲突,待其合并后在 main 上补):
- GET /version 端点(internal/httpapi/server.go)与 cmd/api 的 -version 开关
- compose.yaml 透传 build args(当前由 compose 构建的镜像会报 dev)

⚠️ 未跑 make verify 全量(含 go test ./... 与前端),CI 是本次的测试证据。
2026-10-10 18:13:24 +08:00

61 lines
2.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""打印版本号 —— 唯一来源是仓库根的 CHANGELOG.md。
为什么是它而不是 git tag:CI 检出是 `git fetch --depth 50`,**取不到 tag**,
`git describe --tags` 会失败或者给出误导性的结果。仓库里的文件则一定在。
为什么不是另放一个 `VERSION` 文件:那是第二份副本,发版要改两处,忘一处就漂移
(正本 §3.1)。CHANGELOG.md 里本来就必须写版本号,让它当源头,副本数为一。
用法:
python3 scripts/version.py # 打印版本号;解析不到就以非 0 退出
python3 scripts/version.py --check # 同时跑结构自检(等价 check-changelog.py)
退出码非 0 表示"CHANGELOG 里没有已发布版本条目" —— 构建应当据此失败,
而不是回落成一个看起来正常的假版本号。
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CHANGELOG = ROOT / "CHANGELOG.md"
# 已发布版本标题:`## [0.1.0] - 2026-10-10` / `## [0.1.0]`;`## [Unreleased]` 不算
RELEASE_HEADING = re.compile(r"^##\s*\[(\d+\.\d+\.\d+)\]", re.MULTILINE)
def current_version(path: Path = CHANGELOG) -> str | None:
"""取最上面那个已发布版本号;没有就返回 None。"""
if not path.is_file():
return None
m = RELEASE_HEADING.search(path.read_text(encoding="utf-8"))
return m.group(1) if m else None
def main(argv: list[str]) -> int:
v = current_version()
if v is None:
print(
"❌ CHANGELOG.md 里找不到已发布版本条目(形如 `## [0.1.0] - 2026-10-10`)。\n"
" 版本号解析不到 ⇒ 构建应当失败,而不是回落成假版本号。",
file=sys.stderr,
)
return 1
if "--check" in argv:
sys.path.insert(0, str(Path(__file__).resolve().parent))
from check_changelog import check # type: ignore # noqa: PLC0415
problems = check()
if problems:
for p in problems:
print(f"❌ {p}", file=sys.stderr)
return 1
print(v)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))