我把 GitHub 项目从本地上传到版本 Release 做成了一个 Skill:提交、Tag、归档、校验一次完成
"帮我把这个项目上传到 GitHub"听起来只有三步:
git add
git commit
git push
但只要目标从"临时备份"变成"正式开源",真正要处理的事情会迅速变多:
- 项目里是否混入 .env、Token、私钥或带凭据的 URL?
- 缓存、日志、虚拟环境、构建产物和大文件哪些应该进入 Git?
- 本地 origin 是否真的指向用户给出的仓库?
- 远端已经有提交时,本地历史能不能普通 fast-forward?
- 版本号从哪里来,Tag 是否已经存在?
- Release Notes、源码 ZIP、附加资产和 SHA-256 校验和如何一起生成?
- git push 或 gh release create 返回 0 以后,怎样证明 GitHub 上的分支、Tag 和资产就是这次结果?
我之前已经做过一个 github-safe-publish,它解决的是"审计本地项目,然后安全提交、普通推送并校验远端分支"。这次我没有继续把所有功能塞进旧入口,而是新建了一个覆盖完整项目发布流程的 Skill:
github-project-publisher
开源仓库:
https://github.com/wangzifan396-wzf/skills
本文对应提交:
https://github.com/wangzifan396-wzf/skills/commit/7a90e7eede0c5029c131d7496ee479ae8353f539
项目使用 MIT License。这个新 Skill 的目标是:让用户只面对一个入口,内部再由模块化脚本完成审计、Git 操作、版本准备、Release 创建和独立验证。
它可以自动完成很多动作,但不会把"自动"理解成"默认拥有所有写权限"。默认命令始终是 dry-run;提交和推送需要显式 --execute;创建 GitHub Release 则必须同时出现 --release --execute。
先定义"发布完成"到底是什么
如果把完成条件写成"git push 没报错",这套工具其实不需要做成 Skill。
我给 github-project-publisher 设定的完成条件是:
- 本地发布边界已经审计,且不存在敏感信息或 GitHub 100 MiB 单文件阻断;
- 目标仓库与用户确认的 OWNER/REPOSITORY 完全一致;
- 非空远端必须属于同一段历史,并能通过普通 fast-forward 更新;
- 暂存内容通过 git diff --cached --check;
- 分支推送后,远端精确 ref 与本地最终 HEAD 一致;
- 创建 Release 时,版本 Tag、源码 ZIP、Release Notes、显式资产和 SHA256SUMS.txt 都来自最终提交;
- GitHub Release 创建后,再次查询 Tag、Release 和资产名称,而不是只相信一次 CLI 退出码;
- 全过程生成机器可读 receipt,保留候选文件、计划动作、最终提交和验证结果。
这组条件决定了 Skill 的结构:AI 负责理解用户目标、确认授权和解释异常,Python 脚本负责可重复、低自由度、容易验证的动作。
一个入口,内部仍然模块化

图 1:用户只调用一个 Skill,内部依次完成本地审计、远端检查、dry-run、普通推送、版本产物准备和独立验证。完成条件是远端精确结果匹配,不是命令"看起来成功"。
我最终选择的是"一个用户面对的综合 Skill,内部脚本模块化",而不是把审计、Tag、归档和 Release 拆成四五个需要用户手工串联的小 Skill。
原因很直接:这些步骤共享同一批关键事实。
- 它们必须指向同一个 Git 根目录;
- 必须使用同一个最终 HEAD;
- 必须使用同一个目标仓库和分支;
- 版本 Tag、源码归档和 Release Notes 不能来自不同时间点;
- 校验必须针对本次真正推送后的结果。
如果拆成多个独立用户入口,调用者就要负责在不同 Skill 之间传递提交号、版本号、资产目录和授权状态。任何一次遗漏,都可能让"上传成功"和"Release 正确"变成两回事。
所以对外只保留:
github-project-publisher
内部则分为三个文件:
github-project-publisher/
├── SKILL.md
├── agents/
│ └── openai.yaml
├── references/
│ ├── github-cli.md
│ ├── release-schema.md
│ └── safety-policy.md
└── scripts/
├── publisher_lib.py
├── publish_project.py
└── verify_publish.py
publisher_lib.py 处理审计、Git、远端解析、归档、校验和与验证等确定性逻辑;publish_project.py 负责编排 dry-run 和执行模式;verify_publish.py 刻意保持独立,用第二次查询验证发布结果。
详细授权规则、receipt 字段和 GitHub CLI 前置条件放进 references/,按需读取,避免每次调用都把所有细节塞进上下文。
完全自动化不等于默认拥有写权限

图 2:默认只读预览;--execute 开启提交和普通推送;--release --execute 才开启版本 Release。敏感信息、大文件、历史冲突、已有 Tag、远端冲突或验证不一致都会立即停止。
这类工具最容易出现的误解是:既然目标是"全自动上传",为什么还要 dry-run 和多个显式参数?
因为"自动执行流程"和"自动扩大权限"是两件不同的事。
默认模式:只生成完整计划
不加 --execute 时,发布器会读取项目和远端,输出:
- Git 根目录、当前分支和 HEAD;
- 工作树状态、现有 remotes;
- 候选文件数、候选体积和具体路径;
- Secret、大文件、README、LICENSE 和 .gitignore 检查;
- 目标分支是否为空、是否属于同一历史;
- 将要执行的 Git 操作;
- 版本来源、Tag、Release 资产和产物目录;
- blocker、warning 与最终状态。
但它不会暂存、提交、增加远端、推送或创建 Tag。
--execute:只开放分支发布
显式增加 --execute 后,Skill 才允许:
- 在没有同名远端时增加目标远端;
- 对非空目标执行 fetch 和祖先关系检查;
- 暂存审计通过的项目边界;
- 执行 git diff --cached --check;
- 有变更时创建普通提交;
- 使用普通 git push --set-upstream 推送;
- 重新查询远端分支 SHA。
这里没有任何 force 参数。如果非空远端不是本地 HEAD 的祖先,流程会停止并解释,而不是自动合并无关历史或覆盖目标仓库。
--release --execute:再开放版本发布
Release 是另一层外部写入,所以还要显式增加 --release。
只有两个开关同时存在,且版本、Tag、GitHub CLI 登录状态、资产和远端历史都通过检查后,才会创建 Tag 与 GitHub Release。
这种设计多敲了两个参数,但也让 receipt 可以明确回答:这次到底只是计划、发布了分支,还是创建了一个公开版本。
本地审计具体检查什么
发布器不是通用杀毒软件,也不代替 GitHub Secret Scanning,但它会阻断最常见的开源事故。
敏感信息
检查范围包括:
- .env、私钥、keystore 和凭据类文件;
- GitHub、AWS、Slack、OpenAI 等常见 Token 形态;
- password、client_secret、api_key、access_token 等疑似非占位赋值;
- 把用户名、密码或 Token 嵌进 HTTPS 远端 URL 的写法。
报告只记录规则、路径和行号,不复制匹配到的秘密值。否则"安全扫描报告"本身就可能变成新的泄露文件。
文件大小与发布边界
普通 GitHub 仓库的单文件硬限制是 100 MiB,因此超过这一阈值直接阻断。较大的文件也应该被显式复核:它可能是应该忽略的生成物,也可能更适合作为 Release 附件或 Git LFS 内容。
Skill 不会看到 dist/、build/、图片或视频就一律删除。不同项目的交付边界不同,脚本负责把差异暴露出来,维护者负责决定它们究竟是源码、发布产物还是本地缓存。
README、LICENSE 与 Git 状态
README、LICENSE 和 .gitignore 会进入 readiness 检查,但 Skill 不会擅自替维护者选择许可证,也不会虚构运行命令、测试结果和兼容性。
如果项目已经有暂存内容,发布器会把它纳入审计,而不是先清空索引。用户已有的工作树变化也不会被回滚。
远端为什么不能"地址对了就直接推"
一个 GitHub URL 可能合法,但仍然不是应该推送的目标。
发布器要求 URL 与下面这个显式确认完全一致:
--confirm-repository OWNER/REPOSITORY
如果本地已经有同名远端,但 URL 指向别处,Skill 会拒绝静默执行 git remote set-url。维护者可以检查是不是选错目录、选错仓库,或者在明确情况下改用新的 remote name。
对非空远端,默认同样会停止。只有显式 --allow-existing,并且远端目标分支已经是本地 HEAD 的祖先,才进入普通 fast-forward 更新。
对应的判断可以写成:
empty remote
-> allow first publication
non-empty remote + no --allow-existing
-> stop
non-empty remote + --allow-existing + remote branch is ancestor of local HEAD
-> allow ordinary fast-forward push
non-empty remote + unrelated or diverged history
-> stop
这里有意不自动执行 --allow-unrelated-histories,也不 force push。自动化工具无法只凭 URL 判断远端历史是否应该被本地项目覆盖。
Release 不是再执行一次 push

图 3:版本号、最终 HEAD、Git 历史和显式资产进入同一条打包链,输出 Tag、Release Notes、源码 ZIP、资产副本、SHA256SUMS.txt 和机器可读 receipt。
分支发布解决"GitHub 上有最新代码",Release 解决的是"这个版本有哪些稳定、可下载、可校验的产物"。
新 Skill 支持从以下位置获取版本:
- 命令行 --version 1.2.3;
- package.json;
- pyproject.toml;
- Cargo.toml。
版本会统一规范化成:
v1.2.3
如果本地或远端已经存在同名 Tag,流程直接停止,不覆盖旧版本。
Release Notes 从 Git 历史生成。第一次发布使用当前历史;后续版本可以从最近的版本 Tag 开始整理提交。这里不是让 AI 随意写一篇宣传文案,而是让版本说明保持可追溯。
源码包使用最终提交执行 git archive。这意味着 ZIP 只包含 Git 中的正式内容,不会把工作区缓存、未跟踪文件或 Release 输出目录再次打包进去。
额外资产必须通过 --asset 显式指定。例如:
--asset dist/project-windows.zip
--asset dist/project-linux.tar.gz
Skill 会把源码 ZIP 与这些资产复制到项目外的 Release 产物目录,并为每个上传文件生成 SHA-256:
SHA256SUMS.txt
最后通过已经认证的 GitHub CLI 创建 Release。Skill 不读取 Token,也不要求用户把 Token 粘贴到聊天中;它只使用系统现有的 gh 登录状态。
三种实际用法
1. 先做 dry-run
Windows PowerShell:
python github-project-publisher\scripts\publish_project.py `
--project D:\path\to\project `
--remote https://github.com/OWNER/REPOSITORY `
--confirm-repository OWNER/REPOSITORY `
--branch main `
--commit-message "feat: publish project" `
--receipt D:\path\to\dry-run.json
这条命令不修改 Git 状态。先检查 receipt 中的候选文件、远端、blocker、warning 和 operations。
2. 发布或更新分支
确认计划后,在同一条命令增加:
--execute
如果远端已经属于同一项目历史,再显式增加:
--allow-existing
完整示例:
python github-project-publisher\scripts\publish_project.py `
--project D:\path\to\project `
--remote https://github.com/OWNER/REPOSITORY `
--confirm-repository OWNER/REPOSITORY `
--allow-existing `
--branch main `
--commit-message "feat: publish project" `
--execute `
--receipt D:\path\to\publish-receipt.json
3. 创建版本 Release
先确认:
gh auth status
然后执行:
python github-project-publisher\scripts\publish_project.py `
--project D:\path\to\project `
--remote https://github.com/OWNER/REPOSITORY `
--confirm-repository OWNER/REPOSITORY `
--allow-existing `
--version 1.2.3 `
--release `
--asset dist\project-windows.zip `
--execute `
--receipt D:\path\to\release-receipt.json
如果不需要额外二进制资产,可以省略 --asset,仍然会生成源码 ZIP、Release Notes 与 SHA256SUMS.txt。
我怎样验证它真的完成了发布

图 4:仓库 24/24 测试通过,真实发布审计得到 58 个候选文件、0 blocker、0 warning;独立校验再次读取 GitHub main,本地和远端均指向 7a90e7e...。
验证分成三层。
第一层:标准库单元测试
仓库执行:
python -m unittest discover -s tests -v
结果是 24/24 通过,其中 4 个测试专门覆盖 github-project-publisher:
- GitHub 仓库 slug 与版本号规范化;
- Secret 和大文件阻断只报告规则与路径,不泄露原值;
- Release Notes 与源码归档可复现;
- 针对本地 bare remote 执行真实 commit、普通 push 和远端 SHA 验证。
本地 bare remote 很重要,因为它让执行路径不依赖临时公开仓库,也能测试真正的 Git 写入、fetch、push 和独立验证,而不是只 mock 子进程返回值。
官方 skill-creator 校验结果:
Skill is valid!
第二层:用真实 GitHub 仓库完成自发布
这次新 Skill 正式开源时,我先执行 dry-run,再让它把自己提交并推送到:
https://github.com/wangzifan396-wzf/skills
真实执行 receipt 中的结果:
| 项目 | 结果 |
|---|---|
| 候选文件 | 58 |
| 候选总大小 | 287,679 B |
| blocker | 0 |
| warning | 0 |
| 最终分支 | main |
| 最终提交 | 7a90e7eede0c5029c131d7496ee479ae8353f539 |
| 分支校验 | true |
对应提交包含 10 个变更文件,新增 1,102 行、删除 3 行。
第三层:脱离发布器再次验证
发布完成后,单独运行:
python github-project-publisher\scripts\verify_publish.py `
--project D:\path\to\project `
--remote https://github.com/wangzifan396-wzf/skills `
--branch main `
--json-out D:\path\to\independent-verification.json
得到:
{
"localHead": "7a90e7eede0c5029c131d7496ee479ae8353f539",
"remoteBranch": "7a90e7eede0c5029c131d7496ee479ae8353f539",
"branchMatches": true,
"verified": true
}
这次真实公开验证执行的是 branch-only 发布。
Release 功能已经实现,并通过本地 Git 仓库闭环测试覆盖版本、Notes、源码归档和校验和逻辑,但我没有为了文章测试额外创建一个没有实际版本意义的公开 GitHub Release。这一点必须明确,否则"功能已实现"很容易被误写成"已经在公开仓库创建 Release"。
为什么新建 Skill,而不是直接改旧项目

图 5:旧 Skill 保留"安全上传"的窄语义;新 Skill 继承审计与普通推送,再增加版本、Tag、归档、校验和与 GitHub Release。
github-safe-publish 并没有因为新 Skill 出现就失去价值。
如果用户只需要:
- 审计本地项目;
- 准备 .gitignore;
- 首次上传或同历史普通更新;
- 推送后校验远端分支;
那么原来的窄 Skill 更容易理解,也有更小的依赖和授权面。
而 github-project-publisher 面向的是完整项目发布:
- 保留本地审计和远端历史保护;
- 增加版本号检测与 vX.Y.Z Tag;
- 增加 Git 历史 Release Notes;
- 增加 git archive 源码 ZIP;
- 增加显式 Release 资产;
- 增加 SHA256SUMS.txt;
- 增加 GitHub CLI 创建 Release;
- 增加分支、Tag 和资产的独立校验。
如果直接升级旧 Skill,原本一句"安全上传到 GitHub"就可能隐含创建公开 Release 的权限。新建入口可以让触发语义更清楚,也保留旧流程的稳定性。
这并不是把功能拆散。对需要完整发布的人来说,新 Skill 仍然是一个整体;只是两个入口分别服务"安全 push"和"版本化发布"两种不同意图。
怎样安装和调用
Windows PowerShell:
git clone https://github.com/wangzifan396-wzf/skills.git
Copy-Item -Recurse .\skills\github-project-publisher `
"$HOME\.codex\skills\github-project-publisher"
macOS / Linux:
git clone https://github.com/wangzifan396-wzf/skills.git
cp -R skills/github-project-publisher ~/.codex/skills/github-project-publisher
重启 Codex 后,可以直接说:
使用 $github-project-publisher,把当前项目发布到
https://github.com/OWNER/REPOSITORY。
先做 dry-run,检查敏感信息、文件大小、远端历史和完整操作计划;
确认没有 blocker 后再提交、普通推送,并独立验证远端 main。
需要 Release 时再明确:
使用 $github-project-publisher,把当前项目以 1.2.3 发布到现有 GitHub 仓库。
先审计和 dry-run;通过后创建普通提交与 push、v1.2.3 Tag、源码 ZIP、
Release Notes、显式资产和 SHA256SUMS.txt,再创建 GitHub Release 并核对所有资产。
不要 force push,不要覆盖已有 Tag,不要改写已有远端 URL。
运行依赖分两层:
- 分支发布只需要 Python 3.10+ 和系统 Git;
- GitHub Release 额外需要已经安装并登录的 GitHub CLI gh。
认证继续交给 Git Credential Manager、SSH Agent 或 GitHub CLI 自己管理。Skill 不要求把 Token 放进参数、文件或聊天记录。
当前边界
V1 故意没有把所有 GitHub 能力都塞进来。
它不会自动:
- 创建 GitHub 仓库;
- 修改仓库 visibility、description、topics 或组织设置;
- 开启 GitHub Pages;
- 创建 Issue、Pull Request 或 Discussion;
- force push、覆盖已有 Tag 或重写 Git 历史;
- 清洗已经进入 Git 历史的秘密值;
- 配置 Git LFS、子模块或组织 SSO;
- 决定第三方二进制资产是否拥有再分发权;
- 把 CSDN、视频平台或其他外部发布权限顺带包含进来。
目标仓库默认也要求用户已经创建。创建仓库涉及 owner、visibility、组织策略、初始化文件和权限等新决策,不应该被一句"上传项目"静默包含。
未来如果增加仓库创建,我更倾向于继续放在同一个用户入口里,但要求单独的显式参数和 preview 字段,例如 --create-repository --visibility public,而不是在找不到远端时自动创建。
文章配图如何复现
本文封面和 5 张正文图都读取:
docs/images/github-project-publisher/article-data.json
其中提交号、候选文件数、测试数、blocker、warning 和本地/远端 SHA 来自真实 Git 输出、发布 receipt 和独立验证 JSON,不是为了排版手工编造。
生成命令:
Set-Location promo-video
npm.cmd run article:github-project-publisher:images
图片使用 Pillow 生成 1920×1080 封面与 1600×900 正文图。以后测试数、提交或发布结果变化时,只需先更新结构化 JSON,再统一重建图片,避免同一个数字散落在多张图里。
这次真正想解决的问题
"完全自动上传 GitHub"最有价值的部分,不是把十几条命令压缩成一条命令,而是让这条命令仍然能回答:
- 它准备上传什么?
- 它为什么拒绝某个文件或远端?
- 它获得了哪一层写权限?
- 它有没有改写历史或远端配置?
- Release 里的每个文件来自哪个最终提交?
- 最后谁证明 GitHub 上的结果真的一致?
我最终采用的结构是:一个综合 Skill 负责完整用户意图,内部模块化脚本负责确定性执行,dry-run 和显式参数负责权限边界,独立验证器负责给出第二份证据。
这让"全自动"不再等于"遇到任何情况都继续向前",而是:
在边界清楚、证据充分、授权明确时,把重复的发布动作完整做完;遇到历史冲突、秘密值或目标不一致时,自动停止。
项目已经开源:
https://github.com/wangzifan396-wzf/skills
如果你正在把本地工具、网页项目、Python 包或小游戏仓库整理成正式开源项目,可以先安装 github-project-publisher 跑一次 dry-run。它给出的候选文件、远端历史和 Release 计划,往往比直接执行一次 push 更能暴露真正的发布问题。
更多推荐


所有评论(0)