Files
johnQ c74fe71f0a feat(slark-dsh-cicd): 新增 DSH/Slark 发布实战技能(REQ-20261007-0003)
- 新建 skills-dev/slark-dsh-cicd-plugin:plugin.json + skills/SKILL.md +
  skills/references/fork-release-and-post-checks.md
- 覆盖两条发布线(DSH 插件 pack 双资产 / Slark 宿主重打包+签名)、权限与身份三连
  (push:true 才可写,ls-remote 通 ≠ 能 push)、无上游写权限的 fork 发布且原仓库零改动、
  干净单提交 + git diff --stat <OURS> 树一致性 gate、改指向后必须重新 pack、
  draft→publish 与「更新检查看不到新版」的因果、GitHub HTTPS/fetch 卡死/无 macOS timeout
  的判死处置、发布后 SHA 逐项核对(verify-remote 仅 Windows)、app.asar 完整性约束、
  ai-proj staging→released 与 /req done 证据要求及文档接口故障绕过
- 与 slark-cicd 明确分工并交叉引用,不重复 staging/生产/Desktop 细节
- update-plugin-meta.py PLUGIN_MAP 增加 slark-dsh-cicd-plugin (skill/dev)
- generate-marketplace.py devops 分类补充 slark-dsh-cicd;marketplace.json 仅新增该条目
- 校验:claude plugin validate . 通过(警告为该仓库既有 install_name 字段提示)、git diff --check 干净
2026-10-07 17:06:36 +08:00

214 lines
14 KiB
Markdown
Raw Permalink 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.
---
name: slark-dsh-cicd
description: DSH 插件包与 Slark 宿主补丁的发布 runbook。覆盖 npm run pack 双 tgz 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、改指向后必须重新打包、draft→publish 节奏、GitHub HTTPS/SSH 与 fetch 卡死处置、发布后 SHA 核对、app.asar 完整性约束、ai-proj 发布状态对齐。当用户要发布或升级 DSH 插件(dsh-worktable 等)、发布/更新 Slark 宿主补丁、push 或下载 GitHub 失败、插件「检查更新」看不到新版本、需要 fork 发布、或排查 asar 完整性/签名问题时使用。
---
# DSH 插件与 Slark 宿主发布 runbook (slark-dsh-cicd)
面向「把插件包/宿主补丁真正发出去」的可执行清单。默认在**发布机**执行;命令按阶段照做,
每阶段结束先核验再进入下一阶段。
## 适用与分工
两条发布线,**互不依赖**,不要串流程:
| 发布线 | 产物 | 入口 | 权威源 |
| --- | --- | --- | --- |
| A · DSH 插件 | `dist/v<ver>/dsh-worktable.tgz` + `dsh-worktable-<ver>.tgz`(同一验收包) | `<pkg>/npm run pack` | GitHub Release(tag = `v<包内版本>`) |
| B · Slark 宿主补丁 | 重打包 + 签名的 Slark.app(含内嵌 DSH runtime) | 宿主自身打包/签名链 | 宿主发布链,不经 GitHub Release |
与同市场技能的分工,避免重复:
- **本技能**:DSH 插件包发布、宿主补丁打包侧的完整性约束、发布链本身(权限、tag、Release、SHA 核对)。
- `slark-cicd`:Slark 仓库 staging 验收、生产发布、Desktop 安装包上传 OSS、PR/CI 门禁——服务器与包分发细节以它为准。
- `dev-cicd`:通用流水线设计与排查(Gitea Actions/缓存/并发)。
- `ai-proj-cicd-release`:AI-Proj 服务自身的门禁与回滚。
## 铁律(先记死)
1. **打包只走唯一入口**:`npm run pack`(或仓库自带的等价脚本)。手写 `tar`/`npm pack` 会绕过结构断言与安装导入验证。
脚本自己会做:版本一致性 → 兼容回归 → 构建 → 语法检查 → 结构断言(精确文件清单、无 `src/`、无 `.map`)→
独立临时目录真实安装 + `import()` 断言 → 客户端/宿主门禁 → 双资产同源 → SHA-256。
2. **`package.json` 与 `dsh.plugin.json` 的版本必须相等**,否则构建与打包脚本 fail-closed。发版只升这一处对,不要只改一边。
3. **产物出了包就不再改**:任何源码/常量/文档改动都可能改变 bundle → 旧 `.tgz` 立即作废、sha 必变。**改完必须重新 pack 并重新记录 sha**。
4. **对上游仓库零改动是硬指标**(尤其是没有写权限的外部作者仓库):不 push、不打 tag、不建 Release/PR,一次都不做。
5. **公开发布必须是 draft 起步**,对外动作经用户过目后再 `--draft=false`。
6. **force push 只允许发生在自己 fork 的分支/tag 上**;绝不对上游使用 `-f`。
7. **发布用的分支必须是干净单提交**,不要为了省事把本地历史推上去(本地历史可能含个人路径、凭据、临时调试 commit)。
8. **"下载通了" ≠ "hash 对了"**:发布完成的判据是「远端资产 sha == 本地验收 sha」,不是页面能打开。
## 阶段 0:身份与权限三连(先判定能力,再决定链路)
```bash
gh auth status # 登录账号是否符合预期
ssh -T git@github.com 2>&1 | head -3 # SSH 身份(回显 Hi <user>! 即通)
gh api repos/<owner>/<repo> --jq '.permissions' # 关键:{"pull":true,"push":<bool>}
```
- **只有 `push:true` 才能走「上游直发」**;`push:false` 时不要硬撞,直接走阶段 2 的 fork 发布。
- **`git ls-remote` 通 ≠ 能 push**:读权限随时可通,写权限被拒要到 push 才暴露。不要用 ls-remote 成功推断可写。
- 同时确认远端协议:`git remote -v`。HTTPS 到 GitHub 可能整体不通(见「网络与链路故障处置」),
必要时改用 SSH:`git remote set-url origin git@github.com:<owner>/<repo>.git`。
## 阶段 1:发布线 A — DSH 插件(有上游写权限)
```bash
export HOME="/Users/johnq"
export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:/opt/homebrew/bin:$PATH"
cd <repo>/<pkg>
npm run check && npm run test:gate && npm run pack
shasum -a 256 dist/v<ver>/*.tgz # 两个文件必须同 sha
tar -xzf dist/v<ver>/dsh-worktable.tgz -C "$(mktemp -d)" && grep -rn "<旧仓库名>" <解包目录> # 期望无输出
git add -A && git commit -m "release: <name> v<ver>(REQ-…)…"
git tag -f v<ver> && git push origin HEAD v<ver>
gh release create v<ver> dist/v<ver>/dsh-worktable.tgz dist/v<ver>/dsh-worktable-<ver>.tgz \
--draft --title "<name> v<ver>" --notes-file <notes.md>
```
- tag **必须等于包内版本**(`v` + `package.json#version`),否则下游按 tag 拼版本化资产名会 404。
- Release 附**双资产**:固定名(供 `releases/latest/download/<fixed>.tgz` 长期入口)+ 版本化名(可追责)。
## 阶段 2:发布线 B — 无上游写权限 → fork 发布
```bash
gh repo fork <upstream-owner>/<repo> --clone=false # 已存在则复用
git remote add fork git@github.com:<me>/<repo>.git # 已存在则 git remote set-url fork …
git fetch --no-tags origin main
```
### 2.1 干净单提交 + 树一致性 gate(必做,不可省)
```bash
OURS=$(git rev-parse <本地完成发布内容的 HEAD>)
git checkout -B release/v<ver> origin/main
git rm -rq --cached .
git checkout "$OURS" -- .
git add -A
git commit -m "release: <name> v<ver>(REQ-…)…"
git diff --stat "$OURS" # 必须是空输出
```
- `git diff --stat "$OURS"` **非空即 abort**:切回原分支、删掉该发布分支、报告差异,不要「差一点也推」。
- 该 gate 的真实作用是:让公开发布分支的**树**等于本地验收树,同时**不携带本地历史提交**(个人路径、调试、凭据)。
- `git rm -rq --cached .` 只清索引,随后 `git checkout "$OURS" -- .` 覆盖工作树;若上游 `main` 存在本地树里**已删除**的
文件,残留文件会被 `git add -A` 重新加回导致 gate 失败——先确认 `git diff --name-status origin/main <OURS>` 无 `D`/`R`。
- 提交前可顺手确认没有敏感路径混入:`git log -S'<本机绝对路径前缀>' --oneline`。
### 2.2 推分支 + tag + 附双资产的 draft Release
```bash
git push -u fork release/v<ver>
git tag -f v<ver> && git push -f fork v<ver> # -f 只作用于 fork 的 tag
gh release create v<ver> dist/v<ver>/dsh-worktable.tgz dist/v<ver>/dsh-worktable-<ver>.tgz \
--repo <me>/<repo> --draft --title "<name> v<ver>" --notes-file <notes.md>
```
- 推完复核「原仓库零改动」:`git ls-remote <origin-url>`(应无新分支/tag)+ `gh release list --repo <upstream>`(列表应与之前一致)。
- Release notes 至少写:新增 / 修复 / 清理 / 验证(实际跑过的测试与数字)/ 安装命令(指向 fork)/ 包 SHA-256 / 已知边界。
## 阶段 3:改指向(换仓库、换发布方时)
只要「发布方」变了,插件里所有指向旧仓库的常量都要一次改全,否则**更新检查读的还是旧仓库 → 永远发现不了新版**:
- 更新检查仓库常量(如 `UPDATE_REPO`,可能有多份:入口 + 独立模块 + 设置面板各自实现);
- 升级命令/提示词里的安装 URL(可能拼在字符串里);
- `package.json#repository.url`;
- 发布后核对脚本里硬编码的 REPO;
- README/AGENTS/安装说明里的一行命令、`git clone` 地址、文档链接;
- 任何 `grep -rn "<旧 owner>"` 的残留。
规则:
- **只改指向,不改版本号与逻辑**;逐处 `git diff` 复核。
- 「相关项目/其它仓库」的外链如果指向**另一位开发者的独立仓库**,不要顺手改指(目标仓库不存在会变成死链)——保留并在交付里说明。
- **改完必须重新 pack**(铁律 3),解包 `grep` 确认产物内无旧仓库名,sha 与 Release 全部按新值重来。
## 阶段 4:draft → publish
```bash
gh release edit v<ver> --repo <me>/<repo> --draft=false
```
- **draft 期间 `releases/latest` 仍指向上一个已发布版本** → 插件内「检查更新」看不到新版本;
draft 资产的 tag 路径也不公开可下(需鉴权)。这是「更新检查不生效」最常见的**非 bug 原因**,先查 Release 是否还是 draft。
- 发布前用用户过目确认;发布后再跑阶段 5。
## 阶段 5:发布后核对
优先用仓库自带的核对脚本,但**平台适配要先判定**:`verify-remote.mjs` 之类脚本若用
`powershell Invoke-WebRequest` 下载,则**只在 Windows 可用**,在 macOS 会以 `FAIL: download fixed-name asset` 收场
(它的 GET URL 仍能证明改指生效,不代表脚本思路错)。
macOS/无 PowerShell 时的等价核对(全部要过):
```bash
gh release view v<ver> --repo <me>/<repo> --json assets # 两个资产的 digest 必须都等于本地 sha
gh release download v<ver> --repo <me>/<repo> --dir "$TMP" # draft 也可下载(已鉴权)
shasum -a 256 "$TMP"/*.tgz # 与本地验收 sha 逐字一致
cmp "$TMP/dsh-worktable.tgz" dist/v<ver>/dsh-worktable.tgz # byte-identical
tar -xzf "$TMP/dsh-worktable.tgz" -C "$TMP/ext" && grep -rn "<旧仓库名>" "$TMP/ext" # 无输出
curl -sL -o /dev/null -w '%{http_code}\n' <版本化资产 URL> # 发布后 200/302;draft 阶段 404 属预期
```
结论口径:**文件名 + 结构(精确文件清单)+ 包内版本 + 双资产同源 + 远端=本地验收 SHA + 真实安装导入** 全过才算发布验收。
draft 阶段只能做「远端资产 = 本地验收包」的比对,匿名可下载性要等 publish 后再验。
## 阶段 6:Slark 宿主补丁(宿主侧特有)
- **改 `app.asar` 无效**:Electron 用 `ElectronAsarIntegrity` 记录 asar 头哈希并有 fuse 完整性校验,直接热改会让
哈希失配,破坏签名,应用拒绝启动或行为异常。**必须重打包 + 重新安装**,不要在安装目录里就地改文件。
- 宿主补丁落在 fork/分支上时:rebase 到发布基线后**必须重跑全量测试**(类型检查 + 全量单测),
例如 `pnpm -r typecheck` 与 `node --import tsx --test "src/**/*.test.ts"`;rebase 不产生冲突**不代表语义没变**。
- 宿主发布与插件发布解耦:宿主要用新插件版本,通常靠宿主内嵌 runtime 的升级,而不是「插件先发、宿主自动跟进」。
## 网络与链路故障处置
| 现象 | 判定 | 处置 |
| --- | --- | --- |
| `curl` 访问 GitHub 返回 HTTP `000`(HTTPS 整体不通) | 出口到 github.com:443 被掐断,与凭据无关 | 改走 SSH:`git remote set-url origin git@github.com:<owner>/<repo>.git`,并确认 SSH 身份 |
| `git fetch` 长时间无输出/0 字节、卡十几分钟 | 链路假死,不是"慢" | 判死并清理后重试(见下方循环模板),不要原地等 |
| 30 秒内报 `fatal: early EOF` | 链路被中途掐断(代理/MTU/网络策略) | 记录原文与时间点;换协议/换网络重试;不要反复 force |
| macOS 没有 `timeout` 命令 | BSD 无 GNU timeout | 用后台 + 轮询模板 |
| `git push` 报无权限但 ls-remote 正常 | 只读凭据 | 直接走 fork 发布(阶段 2),不要重试上游 |
macOS 无 `timeout` 时的判死模板:
```bash
( git fetch --no-tags origin main ) & PID=$!
for i in $(seq 1 30); do sleep 1; kill -0 "$PID" 2>/dev/null || break; done
if kill -0 "$PID" 2>/dev/null; then kill -9 "$PID"; pkill -9 -f git-upload-pack; echo "TIMEOUT(30s) — 链路假死"; fi
```
- 认证/网络失败时**不做破坏性重试**(尤其不要为了"绕过"而 force 到上游);把失败原文、已完成的步骤、当前指针状态写清楚上报。
- 推送成功但平台上看不到:查 `git config --get-regexp '^url\.'` 是否有 `pushInsteadOf` 静默改写了目标。
## ai-proj 对齐
- 需求阶段:动手前立项(`create_requirement`,写清需求描述 / 预期结果 / 验收标准三段式 + AC),commit/PR 引用 `REQ-…`。
纯文档/工具/只读排查可例外。
- 交付阶段:**真实发布之后**才推进 `staging → released`;只有计划、没有发布产物或验收证据时不要推进。
- 归档阶段:`/req done` 归档前要**逐条 AC 证据**(命令、输出、资产 sha、URL),不能只写"已完成"。
- **文档接口故障绕过**:若 `append-document-content` / `create-and-attach` 持续报
`document_versions_document_id_version_number_key` 唯一约束错误(同一 `(document_id, version_number)` 冲突),
改用 `PUT /api/v1/requirements/{id}` 把说明写进**需求描述**——`requirements` 表不经过 `document_versions`,可绕开该约束;
不要伪造版本号或删除已有文档版本。
## 故障分流
| 现象 | 处置 |
| --- | --- |
| 插件「检查更新」永远看不到新版 | 依次查:Release 是否还是 draft;`releases/latest` 指向;插件内 `UPDATE_REPO` 是否仍指旧仓库;`latest` 资产名是否与代码拼的一致 |
| pack 报结构断言失败 | 包内混入了 `src/`、`.map` 或多余文件;检查 `files` 字段与构建产物位置,不要手改 tgz |
| 两个 tgz 的 sha 不一致 | 打包脚本的"同源复制"被绕过(手工 pack/改名);重新走唯一入口 |
| `git diff --stat <OURS>` 非空 | 上游 `main` 有本地已删除/重命名文件残留 → 先 `git rm --cached` 清理并确认无 `D`/`R`,gate 不过不上推 |
| 发布分支含个人路径/凭据 commit | 不要 push 该历史;改用干净单提交分支(阶段 2.1) |
| `gh release create` 报 tag 不存在/被占用 | tag 必须已存在或由 `create` 指定;draft 会以 `untagged-…` 预览 URL 呈现,属正常,publish 后即 `.../tags/v<ver>` |
| draft 资产匿名下载 404 | 预期行为(draft 需鉴权);发布后用 `curl -sL -o /dev/null -w '%{http_code}'` 复验 |
| 宿主改完不生效 | 是否只改了 `app.asar`(被完整性校验挡住);必须重打包重装;再查是否加载了另一份 runtime |
## 参考
- `references/fork-release-and-post-checks.md` —— fork 发布可复制命令序列、Release notes 模板、发布后核对矩阵。
- 同市场:`slark-cicd`(Slark staging/生产/Desktop)、`dev-cicd`(流水线设计)、`ai-proj-cicd-release`(AI-Proj 发布链)。
- 权威流程以目标仓库自带的发布脚本与文档为准;本技能是清单,不替代仓库脚本的断言。