---
name: windows-python-release
description: 当要手动发布 Python 包（codewiki-plus）到 PyPI 并建 GitHub Release 时——CI 不发布、Windows 控制台为 GBK、可能无 gh CLI、本机网络对 upload.pypi.org 的 Python TLS 握手有干扰——先核对 tag 区间与工作区未提交改动定 bump 级别，再按 8 步主干执行：同步四处版本→bump 提交（只 add 具体文件）→聚焦+ruff+全量闸门→清 dist 构建→PR 合并 main→tag merge commit 建 Release→uv publish（TLS 超时则 curl.exe 直传）→PyPI JSON API 核对
type: Skill
status: stable
generated:
  by: codewiki/5.8.0
  at: "2026-09-08T06:04:10Z"
stale_after: 2027-01-24
metadata:
  summary: 手动发布 7 步主干 SOP + 每步避坑注解
  source_refs: ["wiki/scenarios/发布与依赖治理方法.md", "notes/2026-09-10-codewiki-手动发布完整流程同步四处版本测试build清distpublishrelease核对.md", "notes/2026-10-26-发版时-tag-区间无新提交发布内容可能在未提交的工作区改动里提交只-add-具体文件.md"]
  revisions: ["at: \"2026-09-08T06:04:10Z\"", {"at": "2026-09-10T02:02:25Z", "reason": "按主干优先重构：原 SOP 8 步中 6 步是避坑，缺发布主流程；改为 7 步主干（每步带判定点），避坑降级为步骤注解。来源为新增的流程型笔记。", "source": "skill_creator"}, {"at": "2026-09-23T04:05:06Z", "reason": "v5.13.0 发布实操回写：①新增步骤 5「PR 合并 main」（check-runs 轮询，非 commit status）；②tag 位置统一为 PR merge commit（main），废弃 v5.12.0 打在 develop bump commit 的旧做法；③步骤 7 补 TLS 握手干扰归因与 curl.exe（schannel）直传 legacy API 绕法，digest 字段名 sha256_digest；④核对端点补版本直查规避 JSON API 缓存延迟。来源：任务「发版本」v5.13.0 发布会话（2026-09-23）。", "source": "skill_creator"}, {"at": "2026-10-26T08:00:29Z", "reason": "v5.16.2/5.16.3 发布实操回写：①新增步骤 0「判定发布内容与 bump 级别」——tag 区间无新提交 ≠ 无发布内容，先查工作区未提交改动；②步骤 2 补提交纪律——只 add 具体文件、fix 与 bump 分 commit、git add -A 会扫入无关未跟踪文件；③步骤 3 闸门细化为聚焦测试→ruff（CI 同口径）→全量；④步骤 6 补 Release API target_commitish 远端自动打 tag 的做法。来源：notes/2026-10-26-发版时-tag-区间无新提交发布内容可能在未提交的工作区改动里提交只-add-具体文件.md（任务「发版本」v5.16.2/5.16.3 会话）。", "source": "skill_creator"}]
  reason: created from candidate materials
  source: skill_creator
  installed_at: "2026-10-26T11:01:51Z"
  installed_to: .codebuddy/skills/windows-python-release/
  installed_hash: "sha256:8d681945e85c6a784325343f0b5b55c172343c100ce7f69149e37cf24c11633e"
---


## 工作场景

手动发布 Python 包（`codewiki-plus`）到 PyPI 并创建 GitHub Release。**CI 只跑测试与 lint，不负责构建与发布**（`.github/workflows/ci.yml:13-74`），因此发布主干没有脚本承载，必须按序手工执行。Windows 控制台默认 GBK，环境可能无 gh CLI。

## 适用条件

- 发布是手动流程，仓库无发布脚本
- 提交信息或 Release 正文含中文/emoji
- 环境无 gh CLI / 未配置 GH_TOKEN，但 git credential manager 已保存凭据

## 核心 SOP

按序执行 8 步，**每步有判定点，不通过就停，不要带着问题往下走**。

0. **判定发布内容与 bump 级别**：`git log <上一tag>..HEAD --oneline` 看提交，**同时 `git status --short` + `git diff --stat` 核对工作区未提交改动**——tag 区间无新提交 ≠ 没有发布内容，发布内容可能全在未提交的工作区里（v5.16.2 实例：log 为空但 fix 改动全在工作区）。据实际内容定 patch/minor bump。
   判定点：发布内容清单明确，bump 级别有依据。

1. **同步四处版本引用**：`pyproject.toml`（`version`）、`codewiki/__init__.py`（`__version__`）、`uv.lock`（用 `uv lock` 刷新，勿手改）、`codewiki/mcp/server.py`（`version=__version__` 注入，勿手写常量）。CLI 与 actor id 也从 `__version__` 读取，自动跟随。
   判定点：四处版本字符串完全一致。

2. **提交 bump 并推送 develop**：消息 `chore: bump version to X`；推 `origin develop`。
   判定点：`git show` 确认版本改动齐全。
   ⚠️ 中文提交信息用 `git commit -F <utf8 文件>`，**不要用 `-m`**（GBK 破坏，乱码或拆词）。
   ⚠️ **只 add 具体文件**：fix 改动与版本 bump 分两个 commit；工作区混着的 repowiki 产物等非发布文件不入库。`git add -A` 会扫入未跟踪的 `.scratch/`、草稿文档等无关文件（v5.16.3 实例：误暂存 157 文件 8 万行），用 `git add -u` + 显式路径精确暂存。

3. **发布闸门（按序）**：聚焦测试（改动模块，秒级）→ `ruff check` / `ruff format --check`（本次变更的 py 文件，与 CI 增量 lint 同口径）→ 全量 `pytest -q`。
   判定点：无失败用例，无 lint 错误。
   ⚠️ 全量 `ruff format --check .` 会报 repowiki/conversations/*.md 内嵌代码块——不在 CI Lint 范围，按 CI 同口径逐文件验证即可。

4. **构建并清理产物**：`uv build`；发布前**清 `dist/`** 或只精确指定本次产物。
   判定点：`dist/` 下只剩本次版本的 wheel 与 sdist。
   ⚠️ 不清 `dist/` 会连带上传旧产物（5.6.0 发布时残留 6 个）。

5. **合并 main**：建 PR develop→main（无 gh CLI 时用 Python `subprocess` 调 `git credential fill` 取凭据再调 REST API，请求体显式 UTF-8）；轮询 head sha 的 **check-runs**（不是 commit status）等 CI 绿后合并。
   判定点：PR merged，main 含 bump commit。

6. **打 tag 并建 GitHub Release**：lightweight tag `vX.Y.Z` 打在 **PR merge commit 上（main 分支）**——Release 应反映 main 的事实状态；不要打在 develop 的 bump commit 上（v5.12.0 旧做法，与 Release 所属分支不一致）。Release 正文经 REST API 创建，显式 UTF-8；也可用 Release API 的 `target_commitish` 让远端自动打 tag。
   ⚠️ Release 正文乱码**不可逆**（UTF-8 被按 GBK 解码写入，字符永久丢失），只能基于 `git log` 事实重写。
   ⚠️ 给 `git credential fill` 喂 stdin 不用 PowerShell 管道（stdin 常为空、引号被破坏），用 `subprocess` 精确传字节；凭据只留进程内。

7. **发布到 PyPI**：优先 `uv publish dist/codewiki_plus-<version>*`（精确指定，默认传 dist/ 全部）。
   **token 来源：Windows 凭据管理器（keyring）**——`keyring.get_password('pypi', 'upload')`，首次配置：`keyring.set_password('pypi', 'upload', '<token>')`。**不要让用户把 token 粘贴到对话里**（本仓库 4 次泄露事故全部源于此），也不要写进仓库内任何文件（gitignore 不是安全边界，`.codebuddy/` 是被跟踪目录）。
   ⚠️ **本机网络对 upload.pypi.org 的 Python OpenSSL TLS 握手会被干扰**：uv publish / twine / requests 全部连接超时（TCP 通、curl 秒通）。绕法：curl.exe（schannel 栈）直传 legacy API——Python 从 whl 的 `*.dist-info/METADATA`、sdist 的 `PKG-INFO` 提取元数据构造完整表单（`:action=file_upload`、`protocol_version=1`、`filetype`、`pyversion` + 全部元数据字段 + `description` 正文），写 curl `-K` 配置文件（`form-string`，命令行会超长），`-u __token__:<token>` POST。**digest 字段名是 `sha256_digest`**（不是 `digests_sha256`，400 报 missing digest）。token 经参数传进程内，用完删临时文件。
   ⚠️ **email.parser 提取元数据时 `Message.keys()` 对重复头返回 N 次**（如 4 个 `Project-URL`），循环内再 `get_all(key)` 会产生 N×N 重复字段（v5.13.1 时 project_urls 重复 16 条被 400 拒绝）。遍历前必须对 key 去重。

8. **核对真实上传**：`https://pypi.org/pypi/codewiki-plus/json` 查最新版本（JSON API 有缓存延迟，可直接查 `/pypi/codewiki-plus/<version>/json`）。
   判定点：API 返回版本 == 目标版本，双产物 size 正确。

## 判断逻辑

- **主干优先**：先有完整流程，避坑是各步的注解，不是流程的替代品。缺任何一步（尤其是 3 闸门与 8 核对）都不算发布完成。
- **tag 区间无新提交 ≠ 无事可发**：发布内容可能全在未提交的工作区改动里，先看 `git status` 再下结论（依据: notes/2026-10-26-发版时-tag-区间无新提交发布内容可能在未提交的工作区改动里提交只-add-具体文件.md 的「tag 区间无提交容易被误判为无事可发」）。
- 乱码/拆词/stdin 失效同源：都是 cmd/PowerShell 中间层破坏参数与编码。统一绕法是「落文件 / 精确 subprocess」，不是加转义。
- TLS 握手超时先做栈归因：TCP 通 + curl（schannel）通 + Python requests 超时 → 是 OpenSSL 栈被网络层干扰，换传输栈（curl.exe）而不是加超时重试。
- PyPI 只增不改会幂等跳过已存在文件，但这是侥幸，不是安全网。

## 禁忌与反模式

- 不要 `git commit -m "中文"`
- 不要 `git add -A` 一把梭——只 add 具体文件，fix 与 bump 分 commit
- 不要不清 `dist/` 就直接 `uv publish`
- 不要跳过闸门（步骤 3）或发布后核对（步骤 8）
- 不要用 PowerShell 管道或 `cmd /c echo` 给 `git credential fill` 喂 stdin
- 不要试图反向还原 GBK 乱码文本（字符已丢失，只能重写）
- PyPI 上传超时不要盲目加超时重试——先区分是读取慢（加超时有用）还是 TLS 握手被掐（换 curl.exe）
- 不要把 PyPI token 写进任何落盘文件后不清理
- 不要把 PyPI token 存进仓库内文件（含 gitignore 的文件）——统一走 keyring（Windows 凭据管理器）
- 不要让用户把 token 粘贴到对话里——对话采集 hook 会把它原样写进 repowiki/conversations/ 并随提交推送

依据: notes/2026-09-10-codewiki-手动发布完整流程同步四处版本测试build清distpublishrelease核对.md 的「7 步主干与每步判定点」；wiki/scenarios/发布与依赖治理方法.md 的「dist 残留被一并上传」「Release 正文乱码不可逆」「Windows 参数与 stdin 编码统一绕法」；任务「发版本」v5.13.0 发布实操（2026-09-23）：TLS 握手干扰归因与 curl.exe 绕法、sha256_digest 字段名、tag 打在 merge commit 的约定；notes/2026-10-26-发版时-tag-区间无新提交发布内容可能在未提交的工作区改动里提交只-add-具体文件.md 的「tag 区间无提交先查工作区」「只 add 具体文件、fix 与 bump 分 commit」「闸门顺序聚焦→ruff→全量」

