Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
510699523b | ||
|
|
7d19d8efd2 |
@@ -405,19 +405,6 @@
|
|||||||
"name": "slark-cicd-plugin",
|
"name": "slark-cicd-plugin",
|
||||||
"source": "./skills-dev/slark-cicd-plugin",
|
"source": "./skills-dev/slark-cicd-plugin",
|
||||||
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||||
"version": "1.1.0",
|
|
||||||
"category": "devops",
|
|
||||||
"keywords": [
|
|
||||||
"devops",
|
|
||||||
"deployment",
|
|
||||||
"operations"
|
|
||||||
],
|
|
||||||
"strict": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slark-dsh-cicd-plugin",
|
|
||||||
"source": "./skills-dev/slark-dsh-cicd-plugin",
|
|
||||||
"description": "DSH 插件包与 Slark 宿主补丁的发布实战技能:npm run pack 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、draft Release、发布后 SHA 核对、asar 完整性与 ai-proj 状态对齐。",
|
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"category": "devops",
|
"category": "devops",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -496,7 +483,7 @@
|
|||||||
"name": "req-plugin",
|
"name": "req-plugin",
|
||||||
"source": "./skills-req/req-plugin",
|
"source": "./skills-req/req-plugin",
|
||||||
"description": "需求工作流管理入口。用于 /req 命令使用、需求生命周期管理、需求撰写(含草稿门禁)。",
|
"description": "需求工作流管理入口。用于 /req 命令使用、需求生命周期管理、需求撰写(含草稿门禁)。",
|
||||||
"version": "2.1.0",
|
"version": "2.0.0",
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"project-management",
|
"project-management",
|
||||||
@@ -509,7 +496,7 @@
|
|||||||
"name": "req-prd-plugin",
|
"name": "req-prd-plugin",
|
||||||
"source": "./skills-req/req-prd-plugin",
|
"source": "./skills-req/req-prd-plugin",
|
||||||
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
|
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
|
||||||
"version": "2.3.0",
|
"version": "2.2.0",
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"project-management",
|
"project-management",
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def load_config():
|
|||||||
def get_category_and_keywords(plugin_name):
|
def get_category_and_keywords(plugin_name):
|
||||||
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
||||||
return "development", ["development", "coding", "workflow"]
|
return "development", ["development", "coding", "workflow"]
|
||||||
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server', 'slark-cicd', 'slark-dsh-cicd']):
|
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server', 'slark-cicd']):
|
||||||
return "devops", ["devops", "deployment", "operations"]
|
return "devops", ["devops", "deployment", "operations"]
|
||||||
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
||||||
return "productivity", ["project-management", "tasks", "requirements"]
|
return "productivity", ["project-management", "tasks", "requirements"]
|
||||||
|
|||||||
+1
-1
@@ -365,7 +365,7 @@ install_plugin() {
|
|||||||
else
|
else
|
||||||
mkdir -p "$dst_dir"
|
mkdir -p "$dst_dir"
|
||||||
# rsync resolved source (handles nested skills/ structures)
|
# rsync resolved source (handles nested skills/ structures)
|
||||||
rsync -a --checksum --delete "$src_dir/" "$dst_dir/"
|
rsync -a --delete "$src_dir/" "$dst_dir/"
|
||||||
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
|
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
|
||||||
ok "$install_name → skill (v$version)"
|
ok "$install_name → skill (v$version)"
|
||||||
INSTALL_ACTION=true
|
INSTALL_ACTION=true
|
||||||
|
|||||||
@@ -608,55 +608,3 @@ fi
|
|||||||
### 非阻断原则
|
### 非阻断原则
|
||||||
|
|
||||||
架构信息缺失时**仅警告不阻止**。用户仍可继续,但会被告知"生成的建议可能不够准确"。
|
架构信息缺失时**仅警告不阻止**。用户仍可继续,但会被告知"生成的建议可能不够准确"。
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 0.5:Shell 环境自检(node/npm command not found 先看这里)
|
|
||||||
|
|
||||||
**在任何 `npm` / `pnpm` / `node` 命令之前,先确认解释器可解析。** 报 `command not found` 时
|
|
||||||
**不要**下结论"当前环境没有 shell",也**不要**去找"给 agent 配 shell"的开关(DSH 没有这个开关)。
|
|
||||||
|
|
||||||
### 根因(不要重复侦察;已在 macOS + Slark/DSH 上定位到代码)
|
|
||||||
|
|
||||||
| # | 事实 |
|
|
||||||
|---|------|
|
|
||||||
| 1 | DSH 的 bash 工具(`@deepseek-ai/dsh-bash-local`)一律以 **`bash -c`(非登录、非交互)** 执行 → **不读** `~/.zprofile` / `~/.zshrc` / `~/.profile` |
|
|
||||||
| 2 | 子进程环境 = **宿主进程环境** − 凭据类变量 − `DSH_*`(`@deepseek-ai/dsh-subprocess`)→ **PATH 不被清洗**,它等于启动宿主那个进程的 PATH |
|
|
||||||
| 3 | 宿主由 GUI 应用(Slark.app)拉起 → 继承 macOS GUI 的最小 PATH(`/usr/local/bin:/bin:/usr/bin:...`)→ node 不在里面 |
|
|
||||||
| 4 | `HOME` **未设置**(不是空值);`USER` / `TMPDIR` / `LANG` 同样未设置 |
|
|
||||||
| 5 | `@deepseek-ai/dsh-shell-env` 只注册/注入 **`DSH_*`** 变量,`PATH` 是保留键 → 改 profile/`cordis.patch.yml` 加 PATH **无效** |
|
|
||||||
|
|
||||||
### 正确做法(两行 export,别多一步)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export HOME="/Users/johnq" # 必须补:git 等直接读 $HOME
|
|
||||||
export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:$PATH" # node/npm/pnpm
|
|
||||||
cd <项目> && npm install && npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
**为什么 `HOME` 必须补**(实测):
|
|
||||||
|
|
||||||
- `~` 在 bash 里靠 passwd 查表展开,所以 `ls ~`、`cd ~/x` 仍能用 → **容易误判环境正常**;
|
|
||||||
- 直接读 `$HOME` 的会崩:`git` → `fatal: $HOME not set`(`status` / `commit` / `config --global` 全废);
|
|
||||||
带 `set -u` 的脚本 → `HOME: unbound variable`(例:ai-proj-helper 的 `install-skills.sh`);
|
|
||||||
- node 侧多数无事(`os.homedir()` 走 passwd 兜底,只有 `process.env.HOME` 是 `undefined`)
|
|
||||||
→ **「node 能跑」≠ 环境完整**,别据此跳过 `HOME`。
|
|
||||||
|
|
||||||
- **本机 node 必须用这一支**(定义在 `~/.zprofile`,对齐项目 `engines.node`):
|
|
||||||
`/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin/node` = v22.22.3,npm 10.9.8。
|
|
||||||
- `/opt/homebrew/bin/node` 是**另一支 v26**,不要混用;`/usr/local/bin/node` 不存在。
|
|
||||||
- 备选①:`/bin/zsh -lic '<命令>'`(登录 zsh 会读 `~/.zprofile`)。
|
|
||||||
- 备选②:绝对路径直调 `/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin/node build.mjs`。
|
|
||||||
- 路径变了先 `ls -d /Users/johnq/.local/node-v*-darwin-arm64/bin` 重新取**真实版本号**,不要猜。
|
|
||||||
|
|
||||||
### 交付纪律(配合「Step 0:验证优先」)
|
|
||||||
|
|
||||||
- **禁止**把"没有 shell / 不能构建"写成交付结论:bash 工具是好的,缺的只是 `HOME` + `PATH`。
|
|
||||||
正确表述是"我未执行构建(缺 node PATH,已给用户补 PATH 后的构建命令)"。
|
|
||||||
- 构建/测试没跑就说没跑(`未经构建与实机验证`),**不要**用"构建配置已就绪"之类中间信号冒充验收。
|
|
||||||
- 校验环境的最快方式(输出直接贴进交付):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export HOME="/Users/johnq"; export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:$PATH"
|
|
||||||
node -v && npm -v && git status --short | head -3
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "slark-cicd-plugin",
|
"name": "slark-cicd-plugin",
|
||||||
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||||
"version": "1.1.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: slark-cicd
|
name: slark-cicd
|
||||||
description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、生产前强制 staging 验收、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark(qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。
|
description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、staging 真机预验、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark(qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。
|
||||||
---
|
---
|
||||||
|
|
||||||
# Slark CI/CD runbook
|
# Slark CI/CD runbook
|
||||||
|
|
||||||
驱动 Slark 从改代码到上线的端到端流程。权威流程与完整细节见仓库文档
|
驱动 Slark 从改代码到上线的端到端流程。环境身份与发布门禁见仓库文档
|
||||||
`docs/cicd-staging-production.md`(本技能命令与之一致);本文件是可直接照做的分阶段清单。
|
`docs/cicd-staging-production.md`;当前 governed staging 候选入口以仓库脚本契约和历史成功回执为准。
|
||||||
|
该文档中直接运行 `deploy-staging.sh` 的示例尚未列出当前脚本要求的不可变候选材料。
|
||||||
|
|
||||||
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
||||||
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
||||||
@@ -17,15 +18,12 @@ description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/C
|
|||||||
- 生产发布只接受**最新 `origin/main` 的精确 40 位 SHA**;`HEAD != origin/main` 会被 `deploy.sh` 拒绝。
|
- 生产发布只接受**最新 `origin/main` 的精确 40 位 SHA**;`HEAD != origin/main` 会被 `deploy.sh` 拒绝。
|
||||||
- 生产发布必须在**普通 clone** 中执行,且 `.git` 必须是目录;`release-prod.sh` / `deploy.sh` 会拒绝
|
- 生产发布必须在**普通 clone** 中执行,且 `.git` 必须是目录;`release-prod.sh` / `deploy.sh` 会拒绝
|
||||||
`.git` 为指针文件的 git worktree。不要污染现有 checkout:主目录脏或被占用时,新建干净的临时 clone。
|
`.git` 为指针文件的 git worktree。不要污染现有 checkout:主目录脏或被占用时,新建干净的临时 clone。
|
||||||
- **生产发布前必须先发布并验收 staging,不得跳过**:同一最新 `origin/main` 精确 SHA 必须先完成迁移演练、
|
|
||||||
staging 正式部署、隔离检查和需求功能验收。把证据报告给用户后,必须再次取得明确的生产确认;开始 staging
|
|
||||||
前获得的生产授权不能替代这次确认。未通过、未完成或用户未确认时停止,不得执行生产发布命令。
|
|
||||||
- 审批式部署,永不自动:亲手敲的 `sudo`/`rm -rf`/`dd`/`curl|sh`/包安装是 `destructive`(NEVER_AUTO),
|
- 审批式部署,永不自动:亲手敲的 `sudo`/`rm -rf`/`dd`/`curl|sh`/包安装是 `destructive`(NEVER_AUTO),
|
||||||
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
||||||
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
||||||
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
||||||
(Name=`slark-staging`,2026-08-18 核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
(文档记录 Name=`slark-staging`;2026-10-23 只读核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
||||||
实例 ID 重查并确认 Name/状态;不得凭历史 IP 判断目标。
|
实例 ID 从 AWS 控制面重查并确认 Name/状态/IP;不得凭历史 IP 或 SSH 可达性判断发布目标。
|
||||||
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
||||||
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
||||||
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
||||||
@@ -34,7 +32,7 @@ description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/C
|
|||||||
|
|
||||||
## 选择链路
|
## 选择链路
|
||||||
|
|
||||||
- 「发布/上线/部署生产」→ 生产发布链:阶段 1→2→3(同 SHA staging 验收)→ 人工确认 →4→5。
|
- 「发布/上线/部署生产」→ 生产发布链:阶段 1→2→4→5。
|
||||||
- 「Desktop/桌面版打包、发布、上传 OSS」→ 阶段 1→2 后走 `docs/desktop-oss-release.md` 的 m5max 双平台链路。
|
- 「Desktop/桌面版打包、发布、上传 OSS」→ 阶段 1→2 后走 `docs/desktop-oss-release.md` 的 m5max 双平台链路。
|
||||||
- 「盯 CI/合并 PR」→ 阶段 1→2(用 babysit 心态循环到 green + mergeable + 评论收口)。
|
- 「盯 CI/合并 PR」→ 阶段 1→2(用 babysit 心态循环到 green + mergeable + 评论收口)。
|
||||||
- 「复现/验证运行时行为、别碰生产」→ staging 预验链(阶段 3,可独立随时跑)。
|
- 「复现/验证运行时行为、别碰生产」→ staging 预验链(阶段 3,可独立随时跑)。
|
||||||
@@ -73,17 +71,13 @@ git push origin --delete <feature-branch> # 删远端
|
|||||||
git branch -D <feature-branch> # 删本地(先切走)
|
git branch -D <feature-branch> # 删本地(先切走)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 阶段 3:staging 真机预验(生产前强制,也可独立运行)
|
## 阶段 3:staging 真机预验(可选,随时)
|
||||||
|
|
||||||
生产候选必须使用最新 `origin/main` 的同一精确 SHA。先执行迁移演练,再正式部署;随后完成隔离检查和本次需求的
|
|
||||||
功能验收。验收不能只看通用 health:凡需求涉及真实集成(如飞书 OAuth),staging 缺少对应 provider/凭据时必须
|
|
||||||
明确标为未完成并停止生产链,除非用户在看到该限制后明确接受。完成后报告目标实例、SHA、Server/Daemon revision、
|
|
||||||
迁移演练、健康、隔离和功能结果,并等待用户再次确认是否进入生产。
|
|
||||||
|
|
||||||
### 3.1 当前权威路径:Slark 独立 staging
|
### 3.1 当前权威路径:Slark 独立 staging
|
||||||
|
|
||||||
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
||||||
公网 IP,因此每次部署前先查询当前 IP;2026-08-18 核验值为 `15.135.112.181`:
|
公网 IP,因此每次部署前先查询当前 IP。`15.135.112.181` 是 2026-10-23 的只读核验值,
|
||||||
|
不是可直接用于将来发布的固定地址:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aws ec2 describe-instances --region ap-southeast-2 \
|
aws ec2 describe-instances --region ap-southeast-2 \
|
||||||
@@ -91,20 +85,93 @@ aws ec2 describe-instances --region ap-southeast-2 \
|
|||||||
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
||||||
--output table
|
--output table
|
||||||
|
|
||||||
|
# 只使用上一步 AWS 返回且已核对 Name=slark-staging、State=running 的公网 IP。
|
||||||
|
export STAGING_IP='<verified-public-ip>'
|
||||||
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
||||||
export STAGING_REMOTE=ubuntu@15.135.112.181
|
export STAGING_REMOTE="ubuntu@$STAGING_IP"
|
||||||
export STAGING_REMOTE_IP=15.135.112.181
|
export STAGING_REMOTE_IP="$STAGING_IP"
|
||||||
export STAGING_BASE=http://15.135.112.181
|
export STAGING_BASE="http://$STAGING_IP"
|
||||||
|
# 使用 Slark 应用内的 project ID,不是 ai-proj 的数字项目/需求/任务 ID。
|
||||||
|
export SLARK_RELEASE_PROJECT_ID='<verified-slark-app-project-id>'
|
||||||
|
export SLARK_RELEASE_TASK_ID='REQ-...'
|
||||||
|
export SLARK_RELEASE_ID='release-staging-<40-sha>-<unique-attempt>'
|
||||||
|
# 普通发布还需从既有受信 provider 提供 mirror token;不要打印或写进日志。
|
||||||
|
|
||||||
scripts/deploy-staging.sh --migrate-dry-run
|
# 先确认候选 SHA 在 canonical GitHub 上可获取,且包含当前 main 的全部提交。
|
||||||
scripts/deploy-staging.sh
|
bash scripts/release-staging-governed.sh '<40-sha>'
|
||||||
STAGING_TOKEN=$(cat /tmp/.staging_token) scripts/staging-seed.sh
|
|
||||||
scripts/staging-verify-isolation.sh
|
scripts/staging-verify-isolation.sh
|
||||||
|
# 使用上一步发布输出的同一 release receipt;观察窗口未结束时保留原 receipt 重试。
|
||||||
|
scripts/complete-staging-verification.sh '<release-receipt-file>'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
这是历史 Slark 发布实际采用的 governed 入口:控制器从 canonical GitHub
|
||||||
|
获取不可变候选、持有本地锁,内层 `deploy-staging.sh` 执行远端构建、数据库迁移、
|
||||||
|
切换和健康检查。需要先单独演练迁移时,按仓库发布文档生成同一精确 SHA 的
|
||||||
|
`staging-candidate` manifest/archive/source,再向 `deploy-staging.sh --migrate-dry-run`
|
||||||
|
传入 `STAGING_CANDIDATE_MANIFEST`、`STAGING_CANDIDATE_ARCHIVE` 和
|
||||||
|
`STAGING_SOURCE_DIR`;当前 `deploy-staging.sh` 不接受缺少这些候选材料的直接调用。
|
||||||
|
这条演练在隔离数据库副本执行 pending up,不重启 Server;正式 governed 发布则在
|
||||||
|
live staging 数据库执行 migrate up。历史 2026-10-22 成功回执见
|
||||||
|
`acceptance/REQ-20261010-0007-rejection-execution-20261022/staging-deploy-r2.log`
|
||||||
|
与 `staging-verify-r2.log`。历史脚本里为特定 DSH UI 发布加入的配对、手动切换及回退步骤
|
||||||
|
不属于普通 Slark Server/daemon 发布命令,不能直接照搬。
|
||||||
|
|
||||||
|
候选若包含 canonical main 尚未收录的 migration,或修改发布控制面,staging admission
|
||||||
|
只允许在隔离 PDV 中验证,不会切换共享 staging。隔离验证通过后,先按 PR 门禁将改动并入
|
||||||
|
canonical main,再用新的 main 精确 SHA 重新准备候选和执行共享 staging 发布;不要把
|
||||||
|
`isolated_required` 的成功回执写成共享 staging 已发布。
|
||||||
|
|
||||||
|
若镜像 token 确实不可读,仓库另有 `deploy-staging.sh --no-mirror --reason '<具体原因>'`
|
||||||
|
审计路径。仅在已按官方 `staging-candidate.mjs` 生成并核验同一精确 SHA 的 manifest、archive、
|
||||||
|
source 和 daemon seed revision 列表,且已满足正常 CI、环境身份和 release admission 门禁时使用;
|
||||||
|
该路径不读取 Bitwarden、不切换 `staging-latest` 镜像频道。历史先例见
|
||||||
|
`docs/reports/REQ-20261012-0006-delivery-status.md`。使用后应明确记录“共享 staging 已发布,
|
||||||
|
镜像频道未更新”,不能把 no-mirror 写成镜像发布成功。
|
||||||
|
|
||||||
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
||||||
`docs/req-20260811-0059-staging-environment-design.md`。
|
`docs/req-20260811-0059-staging-environment-design.md`。
|
||||||
|
|
||||||
|
**AWS 凭证不可用时的只读排查(2026-10-23 记录):**先查 Slark 自己的历史发布记录,
|
||||||
|
例如 `acceptance/REQ-20260930-0015/release-staging-main-history-window.sh`;其中记录的
|
||||||
|
staging IP 是 `15.135.112.181`,Slark 应用 release project ID 是
|
||||||
|
`tpqvorREPTj2kx9poKJWH`。这些值只是定位线索,使用前须重新核对。可用已有
|
||||||
|
`cloud-server-syd.pem`、`StrictHostKeyChecking=yes` 只读 SSH 到历史 IP,并核对远端
|
||||||
|
`/sys/devices/virtual/dmi/id/board_asset_tag`、IMDSv2 `instance-id` 与
|
||||||
|
`public-ipv4`;2026-10-23 三项分别核对为 `i-013e5ca3fdfddc13e`、
|
||||||
|
`i-013e5ca3fdfddc13e`、`15.135.112.181`,当时 `slark-server` 和
|
||||||
|
`slark-daemon-staging` 均在运行。不要输出 IMDS token 或应用凭证。
|
||||||
|
|
||||||
|
只读身份核验示例(历史 IP 仅用于定位;任何一项不匹配就停止):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -i ~/.ssh/cloud-server-syd.pem -o BatchMode=yes \
|
||||||
|
-o StrictHostKeyChecking=yes -o ConnectTimeout=5 \
|
||||||
|
ubuntu@15.135.112.181 'bash -s' <<'STAGING_IDENTITY'
|
||||||
|
set -euo pipefail
|
||||||
|
tag="$(cat /sys/devices/virtual/dmi/id/board_asset_tag)"
|
||||||
|
token="$(curl -fsS --noproxy '*' --max-time 3 -X PUT \
|
||||||
|
-H 'X-aws-ec2-metadata-token-ttl-seconds: 60' \
|
||||||
|
http://169.254.169.254/latest/api/token)"
|
||||||
|
id="$(curl -fsS --noproxy '*' --max-time 3 \
|
||||||
|
-H "X-aws-ec2-metadata-token: $token" \
|
||||||
|
http://169.254.169.254/latest/meta-data/instance-id)"
|
||||||
|
ip="$(curl -fsS --noproxy '*' --max-time 3 \
|
||||||
|
-H "X-aws-ec2-metadata-token: $token" \
|
||||||
|
http://169.254.169.254/latest/meta-data/public-ipv4)"
|
||||||
|
test "$tag" = i-013e5ca3fdfddc13e
|
||||||
|
test "$id" = "$tag"
|
||||||
|
test "$ip" = 15.135.112.181
|
||||||
|
printf 'instance=%s public_ip=%s\n' "$id" "$ip"
|
||||||
|
STAGING_IDENTITY
|
||||||
|
```
|
||||||
|
|
||||||
|
这条 SSH/DMI/IMDS 路径只证明所连主机的实例身份,**不提供 AWS Name 标签的当次证明,
|
||||||
|
不能代替上面的发布前控制面核验**。`docs/runbooks/REQ-20260922-0005-staging-watchdog-emergency-recovery.md`
|
||||||
|
中的替代路径仅授权给那次特定事故,不能移用于普通 staging 发布。不要借用其他项目
|
||||||
|
的 AWS profile;若缺少 Slark AWS 控制面凭证,停在只读排查并记录缺口。2026-10-23
|
||||||
|
实例自带角色 `dsh-identity-staging-ec2` 调用 `DescribeInstances` 返回
|
||||||
|
`UnauthorizedOperation`,不能靠这个角色补齐 Name 核验。
|
||||||
|
|
||||||
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
||||||
|
|
||||||
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
||||||
@@ -133,9 +200,6 @@ routed-remote 要 `network=enabled` 必须同时满足:① agent `workspace_ac
|
|||||||
|
|
||||||
## 阶段 4:生产发布(审批式)
|
## 阶段 4:生产发布(审批式)
|
||||||
|
|
||||||
**前置门禁**:阶段 3 已对同一 SHA 完成 staging 部署和验收,证据已报告,且用户在报告之后明确确认生产发布。
|
|
||||||
缺任一项即停止;不得把 PR 合并授权、较早的生产授权或单纯的“继续”视为验收后确认。
|
|
||||||
|
|
||||||
**唯一姿势**:从干净的普通 clone 发布最新 `origin/main`(`HEAD == origin/main` 精确 SHA)。staging 可用
|
**唯一姿势**:从干净的普通 clone 发布最新 `origin/main`(`HEAD == origin/main` 精确 SHA)。staging 可用
|
||||||
独立 worktree,但生产脚本为保证同步与发布边界,会拒绝 worktree;不要在用户已有脏 checkout 中清理或发布。
|
独立 worktree,但生产脚本为保证同步与发布边界,会拒绝 worktree;不要在用户已有脏 checkout 中清理或发布。
|
||||||
|
|
||||||
@@ -158,17 +222,6 @@ pnpm release:prod # = release-prod.sh → deploy.sh --backend --no-in
|
|||||||
- 命中审批闸(发布/sudo)→ **等人批**;不要绕。脚本内部 SSH 载荷子进程的 sudo 不触发闸。
|
- 命中审批闸(发布/sudo)→ **等人批**;不要绕。脚本内部 SSH 载荷子进程的 sudo 不触发闸。
|
||||||
- 普通工作 clone 发布完切回原分支:`git checkout <branch>`;一次性发布 clone 可保留作审计,清理时用可恢复方式。
|
- 普通工作 clone 发布完切回原分支:`git checkout <branch>`;一次性发布 clone 可保留作审计,清理时用可恢复方式。
|
||||||
|
|
||||||
### HANDOFF_BLOCKED 恢复入口
|
|
||||||
|
|
||||||
发布准备遇到 `HANDOFF_BLOCKED`、handoff manifest=`failed_closed` 或命令状态不确定时,停止发布切换;禁止靠重跑
|
|
||||||
发布脚本、直接改表或默认 replay 来“解卡”。先把 manifest、命令、execution、lease/outbox 和审计证据做只读盘点,
|
|
||||||
再让用户明确授权每条记录的 reconciliation action/outcome 及是否允许继续发布。恢复细节见
|
|
||||||
[references/production-handoff-recovery.md](references/production-handoff-recovery.md);仅遇到该类事故时读取。
|
|
||||||
|
|
||||||
`COMMAND_AUTHORITY_CHANGED` 是有效并发护栏,不是可忽略错误:重新读取当前 authority/transport/execution generation,
|
|
||||||
确认期间没有新活动,再在原授权范围内幂等续办。若发现新工作、活跃 lease/outbox 或无法解释的副作用,立即停止并
|
|
||||||
重新请求决策。
|
|
||||||
|
|
||||||
## 阶段 5:发布后核查
|
## 阶段 5:发布后核查
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -176,12 +229,7 @@ scripts/verify-deploy.sh # 健康(前端/api/health=200)、bundle、server/da
|
|||||||
```
|
```
|
||||||
|
|
||||||
预期 `✓ deploy verification passed: server=<sha> daemon=<sha>` 且 `<sha> == origin/main`,舰队 `online ≥ 发布前`。
|
预期 `✓ deploy verification passed: server=<sha> daemon=<sha>` 且 `<sha> == origin/main`,舰队 `online ≥ 发布前`。
|
||||||
完成前还要独立复核 governed release 账本为 `succeeded/complete`、四组件实际 SHA 均为目标 SHA、租约已释放、
|
镜像落后告警(默认 `--no-mirror`)非阻断,需补推按告警的幂等命令(需 Bitwarden session);镜像仅单向 fast-forward。
|
||||||
新 handoff manifest=`verified`;若本次处理过旧 manifest,还要核对其全部 case 已达授权终态且 replay 数为 0。
|
|
||||||
|
|
||||||
默认生产包装器使用 `--no-mirror`:构建 daemon candidate binary 用于运行态验证不等于上传镜像资产。镜像落后告警
|
|
||||||
在该模式下是预期的非阻断结果;不得未经单独授权补传镜像。交付说明要明确国内安装器可能回退 upstream,避免把
|
|
||||||
本地构建、生产 daemon 更新和镜像上传混为一谈。
|
|
||||||
|
|
||||||
## Desktop m5max 双平台发布
|
## Desktop m5max 双平台发布
|
||||||
|
|
||||||
@@ -221,11 +269,6 @@ scripts/git-release-governance.sh status
|
|||||||
| cohost Server 已更新但 daemon 仍是旧 SHA | 用 `--profile daemon up -d daemon-staging` 更新 daemon;核对两容器 revision 后才算完成 |
|
| cohost Server 已更新但 daemon 仍是旧 SHA | 用 `--profile daemon up -d daemon-staging` 更新 daemon;核对两容器 revision 后才算完成 |
|
||||||
| cohost daemon 启动即退出/fatal | 看 `docker logs slark_daemon_staging`;修复凭据/环境或版本契约,禁止用旧 daemon 冒充验收通过 |
|
| cohost daemon 启动即退出/fatal | 看 `docker logs slark_daemon_staging`;修复凭据/环境或版本契约,禁止用旧 daemon 冒充验收通过 |
|
||||||
| verify 失败 SHA 不符 | 部署到了旧码,重发 |
|
| verify 失败 SHA 不符 | 部署到了旧码,重发 |
|
||||||
| `HANDOFF_BLOCKED` / manifest=`failed_closed` | 停止切换并读取 handoff recovery 参考;只读盘点后取得逐项 action/outcome 授权,禁止默认 replay |
|
|
||||||
| reconciliation 返回 `COMMAND_AUTHORITY_CHANGED` | 查当前 generation 与新活动;无新活动才在原授权内幂等续办,有变化则停止重新决策 |
|
|
||||||
| `--no-mirror` 后提示镜像落后 | 预期非阻断;报告 upstream fallback,未经单独授权不上传镜像 |
|
|
||||||
| 生产授权已给但 staging 尚未验收 | 先完成同 SHA staging 全量验收,报告证据并重新等用户确认;不得沿用较早授权 |
|
|
||||||
| staging 缺少需求依赖的真实集成配置 | 将对应验收标为未完成并停止生产链;补齐配置后重验,或让用户基于明确限制作决定 |
|
|
||||||
|
|
||||||
## ai-proj 流程(强制)
|
## ai-proj 流程(强制)
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
# Production handoff recovery
|
|
||||||
|
|
||||||
仅在生产发布被 coding-session handoff 阻塞、manifest 进入 `failed_closed`,或存在 `needs_review` / 不确定命令时读取。
|
|
||||||
目标是保留 fail-closed 语义,在明确授权下终结歧义状态,并证明没有误重放命令。
|
|
||||||
|
|
||||||
## 先冻结发布,不先解锁
|
|
||||||
|
|
||||||
- 停止发布切换和自动重试;记录 release ID、候选 SHA、manifest ID、阻塞发生阶段与原始错误。
|
|
||||||
- 不把“继续”“再试一次”解释为 replay、force-end 或生产发布授权。向用户展示证据后,确认语句应绑定 manifest、
|
|
||||||
条目数量或 command ID、目标 action/outcome、是否禁止 replay,以及要继续发布的精确 SHA。
|
|
||||||
- 不直接更新 handoff、command、execution 表来改变状态。优先走已部署版本的内部治理 API;只有 API 无法恢复且
|
|
||||||
已获得对应操作授权时,才可调用该版本现有 domain repository,并保留 lifecycle event 与 audit log。
|
|
||||||
|
|
||||||
## 只读证据盘点
|
|
||||||
|
|
||||||
对 manifest 中每条 item 核对并保存摘要:
|
|
||||||
|
|
||||||
- command ID、session/project/computer ID、command status、execution state;
|
|
||||||
- manifest 记录的 authority term、transport generation、execution generation,以及当前实际值;
|
|
||||||
- command/execution lease 是否过期,outbox 是否仍有 active/pending 记录;
|
|
||||||
- session 中是否还有其他活跃命令或新事件;
|
|
||||||
- daemon acknowledgement、result/receipt、审计事件是否能证明执行或未执行。
|
|
||||||
|
|
||||||
若 lease/outbox 仍活跃、存在新命令、证据相互矛盾,或无法排除外部副作用,停止恢复并请求新的人工判断。
|
|
||||||
|
|
||||||
## Action 与 outcome 必须匹配证据
|
|
||||||
|
|
||||||
- `no_action / not_executed`:只有权威证据能证明命令从未执行,且治理接口允许该组合时使用。
|
|
||||||
- `terminate / terminated`:用于显式受控终结仍处于 uncertain/active 语义的命令;先通过 domain 操作取消 command、
|
|
||||||
force-end execution、完成 session 并写审计,再完成 reconciliation,不产生 replay。
|
|
||||||
- `mark_failed / failed`:证据确认失败、且不能安全恢复为成功或未执行时使用。
|
|
||||||
- `replay / replayed_success`:仅在用户明确授权 replay 且 exactly-once/幂等边界得到证明时使用。发布解卡绝不能默认 replay。
|
|
||||||
|
|
||||||
不要为了迎合预先选择的 outcome 忽略现场证据;如果授权与可证明事实不兼容,停止并把差异报告给用户。
|
|
||||||
|
|
||||||
## 受治理恢复顺序
|
|
||||||
|
|
||||||
1. 使用精确 manifest/item 建立或读取 reconciliation case。
|
|
||||||
2. 取得具备 action、evidence digest、operator 和有效期约束的审批。
|
|
||||||
3. 记录 decision;执行 domain 终结或治理 apply;随后用权威运行态证据 confirm terminal outcome。
|
|
||||||
4. 若 apply 返回 `COMMAND_AUTHORITY_CHANGED`,不要覆盖或绕过。重新读取当前三类 generation 和新活动:
|
|
||||||
- generation 只因刚完成的受控终结而变化,且无新命令、lease/outbox 时,可在同一授权 action 下幂等续办;
|
|
||||||
- generation 对应新工作或原因不明时,停止并重新审批。
|
|
||||||
5. 每个 item 都 terminal 后,确认 manifest 自动或受治理地进入 `reconciled`,再启动新的发布尝试。
|
|
||||||
|
|
||||||
凭据失败发生在事务前时应视为零变更并记录。不要反复猜测 owner 凭据;不得打印连接串或密钥。若必须使用运行时
|
|
||||||
`slark_app` 连接,只能通过已部署 domain repository、正确的 per-transaction project/user RLS context 和审计路径操作。
|
|
||||||
|
|
||||||
## 零重放与发布恢复验收
|
|
||||||
|
|
||||||
恢复完成至少证明:
|
|
||||||
|
|
||||||
- `terminalCount == totalCount`,每条 decision action 与 terminal outcome 等于用户授权;
|
|
||||||
- 禁止 replay 的场景下,decision action=`replay` 数和 execution replay attempt 增量均为 0;
|
|
||||||
- `terminate/terminated` 场景下,command=`cancelled`、execution=`force_ended`,并存在对应 lifecycle/audit 证据;
|
|
||||||
- 旧 manifest=`reconciled`,没有 active handoff reservation 残留。
|
|
||||||
|
|
||||||
继续生产发布时使用新 governed release / handoff manifest,不复用 failed-closed manifest。重新检查候选仍是最新
|
|
||||||
`origin/main`、同 SHA staging eligibility receipt 仍有效、工作树干净;任何一项变化都回到 staging 与人工确认门禁。
|
|
||||||
|
|
||||||
发布成功后再独立执行一次 `scripts/verify-deploy.sh`,并只读核对:release=`succeeded/complete`、target/actual Web、
|
|
||||||
Server、daemon、artifact SHA 全相等、lease 已释放、finished 已记录、新 manifest=`verified`、舰队在线数不低于基线。
|
|
||||||
将旧 manifest reconciliation 与新发布证据一起回写 ai-proj。
|
|
||||||
|
|
||||||
## `--no-mirror` 的解释边界
|
|
||||||
|
|
||||||
- `pnpm release:prod` 默认走 `deploy.sh --backend --no-install --no-mirror`;不上传 daemon 镜像资产。
|
|
||||||
- 发布过程仍可能在隔离 candidate 目录构建 daemon binary,用于同 SHA 验证或部署运行态 daemon;这不是镜像上传。
|
|
||||||
- mirror freshness 告警表示镜像源仍旧,国内安装器可能回退到较慢 upstream。它不阻断当前生产运行态发布。
|
|
||||||
- 补传 mirror 是单独的外部写操作,需要单独授权和凭据;不能为了消除告警在发布收尾时顺手执行。
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "slark-dsh-cicd-plugin",
|
|
||||||
"description": "DSH 插件包与 Slark 宿主补丁的发布实战技能:npm run pack 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、draft Release、发布后 SHA 核对、asar 完整性与 ai-proj 状态对齐。",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": {
|
|
||||||
"name": "qiudl"
|
|
||||||
},
|
|
||||||
"install_name": "slark-dsh-cicd",
|
|
||||||
"install_type": "skill",
|
|
||||||
"dir_category": "dev"
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
---
|
|
||||||
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 发布链)。
|
|
||||||
- 权威流程以目标仓库自带的发布脚本与文档为准;本技能是清单,不替代仓库脚本的断言。
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# Fork 发布与发布后核对
|
|
||||||
|
|
||||||
本文件是 `slark-dsh-cicd` 的操作附件:需要**无上游写权限发布**,或需要**逐项核对远端资产**时读取。
|
|
||||||
把它当模板:替换 `<...>` 占位符,按阶段执行,每阶段的核验不通过就停下,不要"带病"推进。
|
|
||||||
|
|
||||||
## 0. 变量与前置
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export HOME="/Users/johnq"
|
|
||||||
export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:/opt/homebrew/bin:$PATH"
|
|
||||||
|
|
||||||
UP=<upstream-owner>/<repo> # 例如 Aisland-SJL/dsh-worktable(外部作者,无写权限)
|
|
||||||
ME=<your-account> # 例如 qiudl
|
|
||||||
PKG=<repo>/01_content # 打包脚本所在目录
|
|
||||||
VER=0.4.1 # = package.json#version
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh auth status
|
|
||||||
ssh -T git@github.com 2>&1 | head -3
|
|
||||||
gh api "repos/$UP" --jq '.permissions' # push:false → 必须走 fork 发布
|
|
||||||
git remote -v
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1. 改指向 → 重新打包(顺序不可颠倒)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd "$PKG"
|
|
||||||
grep -rn "<upstream-owner>" .. --include='*.json' --include='*.ts' --include='*.tsx' --include='*.md' --include='*.mjs'
|
|
||||||
# 逐处改完之后再打包:
|
|
||||||
npm run check && npm run test:gate && npm run pack
|
|
||||||
shasum -a 256 dist/v$VER/*.tgz
|
|
||||||
TMP=$(mktemp -d) && tar -xzf dist/v$VER/dsh-worktable.tgz -C "$TMP"
|
|
||||||
grep -rn "<upstream-owner>" "$TMP" ; echo "grep exit=$? (1 = 无残留,正确)"
|
|
||||||
```
|
|
||||||
|
|
||||||
判据:`check`/`test:gate`/`pack` 全部 exit 0;两个 tgz 同 sha;解包无旧仓库名。
|
|
||||||
|
|
||||||
## 2. fork 与干净发布分支
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd <repo-root>
|
|
||||||
gh repo fork "$UP" --clone=false # 已存在会提示 already exists,可复用
|
|
||||||
git remote add fork "git@github.com:$ME/<repo>.git" # 已存在:git remote set-url fork …
|
|
||||||
git fetch --no-tags origin main
|
|
||||||
|
|
||||||
OURS=$(git rev-parse HEAD) # 本地"发布内容已就绪"的 HEAD
|
|
||||||
git diff --name-status origin/main "$OURS" | awk '$1 ~ /^[DR]/' # 期望空;有 D/R 先处理再用 gate
|
|
||||||
|
|
||||||
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-…)<一句话说明>"
|
|
||||||
|
|
||||||
DIFFOUT="$(git diff --stat "$OURS")"
|
|
||||||
if [ -n "$DIFFOUT" ]; then
|
|
||||||
echo "GATE FAILED:"; echo "$DIFFOUT"
|
|
||||||
git checkout <原分支> && git branch -D release/v$VER # abort:不留半成品分支
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "GATE PASSED: release tree == OURS tree"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 推送(只对 fork)与 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 /tmp/rel-notes.md
|
|
||||||
```
|
|
||||||
|
|
||||||
原仓库零改动复核:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git ls-remote "git@github.com:$UP.git" # 无 release/v… 分支、无新 tag
|
|
||||||
gh release list --repo "$UP" # 与发布前一致
|
|
||||||
```
|
|
||||||
|
|
||||||
### Release notes 模板(draft 阶段就写全新值)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# <name> v<ver>
|
|
||||||
> 发布方:fork <ME>/<repo>(原仓库 <UP> 为上游来源)。插件内更新检查已指向本 fork。
|
|
||||||
|
|
||||||
## 新增 / 修复 / 清理
|
|
||||||
## 验证
|
|
||||||
- check / gate / pack 实际输出(测试条数、断言项)
|
|
||||||
- 双资产同 SHA:<sha256>
|
|
||||||
- 解包 grep 无旧仓库名
|
|
||||||
- 发布分支为干净单提交(父提交 = 上游 main),`git diff --stat <OURS>` 为空
|
|
||||||
## 安装
|
|
||||||
网页端 / 桌面端一行命令(指向 fork)
|
|
||||||
## 已知边界
|
|
||||||
- draft 期间 releases/latest 仍指旧版 → 更新检查看不到新版
|
|
||||||
- 保留的第三方外链、平台未验证范围
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. publish 与发布后核对矩阵
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh release edit v$VER --repo "$ME/<repo>" --draft=false
|
|
||||||
```
|
|
||||||
|
|
||||||
| 核对项 | 命令 | 通过判据 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 资产列表与 digest | `gh release view v$VER --repo $ME/<repo> --json assets` | 两个资产 `digest` = 本地 sha |
|
|
||||||
| 下载后 sha | `gh release download v$VER --repo $ME/<repo> --dir "$D"` + `shasum -a 256 "$D"/*.tgz` | 与本地逐字一致 |
|
|
||||||
| 与本地验收件一致 | `cmp "$D/dsh-worktable.tgz" dist/v$VER/dsh-worktable.tgz` | 无差异(byte-identical) |
|
|
||||||
| 包内无旧仓库名 | 解包后 `grep -rn "<upstream-owner>"` | 无输出(exit 1) |
|
|
||||||
| 包内版本 = tag | 解包读 `package.json#version` | `v$VER` |
|
|
||||||
| 匿名可下载 | `curl -sL -o /dev/null -w '%{http_code}\n' <资产 URL>` | publish 后 200/302;draft 期 404 属预期 |
|
|
||||||
| 安装可用 | 临时目录 `npm install <tgz>` + `import()` 断言 | 导出/版本断言通过 |
|
|
||||||
|
|
||||||
- **`verify-remote.mjs` 的平台限制**:其下载走 `powershell Invoke-WebRequest`,只在 Windows 可用;
|
|
||||||
在 macOS 会 `FAIL: download fixed-name asset`。此时用上表的 `gh release view/download` + `shasum` + `cmp` 等价替代,
|
|
||||||
并说明该脚本的 GET URL 已证明改指生效。
|
|
||||||
- **"检查更新"看不到新版**的排查顺序:Release 是否 draft → `releases/latest` 指向 → 插件内 `UPDATE_REPO` 指向 →
|
|
||||||
`latest` 资产名与代码拼法一致。
|
|
||||||
|
|
||||||
## 5. 链路判死模板(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
|
|
||||||
```
|
|
||||||
|
|
||||||
- HTTPS 到 GitHub `curl` 返回 `000` → 整体不通,改 SSH(`git remote set-url`)。
|
|
||||||
- 30 秒内 `fatal: early EOF` → 链路被掐断;记录原文与时间,换网络/协议重试。
|
|
||||||
- 认证或网络失败时不做破坏性重试,也不要把 force 用到上游;保留本地提交并上报失败原文。
|
|
||||||
@@ -322,59 +322,6 @@ npm run lint
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚠️ 既有契约与读模型迁移门禁(必须)
|
|
||||||
|
|
||||||
> **惨痛教训**:新写链路把真实数据写入新表,旧 API 仍读原表,前端虽然没改却从此看不到新数据。“后端已成功执行”不等于“用户能看到执行结果”。
|
|
||||||
|
|
||||||
开发计划涉及新表、新账本、新写路径、替换存储模型或接口版本时,必须先完成以下门禁。未通过时,不得把“新建平行表/新建平行 API”写成默认方案。
|
|
||||||
|
|
||||||
### 1. 先证明旧模型不能复用
|
|
||||||
|
|
||||||
- [ ] 已检查现有表、字段、索引、状态机和扩展字段(如 metadata/JSON)。
|
|
||||||
- [ ] 已说明为什么增加字段、子表或现有类型无法满足需求。
|
|
||||||
- [ ] 新表承载的是真正独立的实体或生命周期,而不是为同一业务实体建立第二个彼此脱节的世界。
|
|
||||||
- [ ] 已区分“内部不可变证据/事件账本”和“产品对外读模型”;新建内部账本不代表必须更换原有产品接口。
|
|
||||||
|
|
||||||
### 2. 真实调用方查证
|
|
||||||
|
|
||||||
必须从对外接口向所有消费方反向追踪,不能只审查新后端写路径:
|
|
||||||
|
|
||||||
- [ ] 列出现有 API 路径、请求参数、响应结构和身份/分页语义。
|
|
||||||
- [ ] 使用 `rg` 查找 Web、iOS、Android、定时任务、报表和第三方调用方。
|
|
||||||
- [ ] 若仓库存在 `.codex-fe-ref/`,必须在其中查证配套前端的真实调用。
|
|
||||||
- [ ] 开发计划的变更文件清单必须覆盖“写入 → 投影/查询 → API → 页面”完整链路。
|
|
||||||
|
|
||||||
### 3. 稳定契约默认不变
|
|
||||||
|
|
||||||
- 既有用户界面已消费的 API,默认保留路径和响应契约;除非用户明确批准破坏性迁移,不得通过新建版本接口把兼容成本转嫁给前端。
|
|
||||||
- 同一业务概念只保留一个产品级读模型。内部账本可以拆分,但必须投影回既有读模型,或由原 API 无感聚合。
|
|
||||||
- 日志、告警、信号或推测数据不得在查询降级时冒充业务事实(例如把 warning 当成交易记录)。
|
|
||||||
|
|
||||||
### 4. 不可避免新写模型时的强制迁移计划
|
|
||||||
|
|
||||||
若确有独立生命周期、不可变审计或 1:N/N:N 证据需求,可以增加新表,但开发计划必须同时包含:
|
|
||||||
|
|
||||||
1. **写入兼容**:双写,或从新账本向既有读模型做事务性/幂等投影。
|
|
||||||
2. **历史回填**:有界、可恢复、可重跑,且不猜测无法证明的归属。
|
|
||||||
3. **去重身份**:定义跨旧/新数据源的稳定业务键,并用数据库唯一约束或等价强保证防重。
|
|
||||||
4. **读取切换**:定义何时以新证据为准、旧数据如何兼容,以及结果数量/金额/状态对账门槛。
|
|
||||||
5. **回滚策略**:回滚新写入时仍能读取已产生的真实业务事实,不删除或隐藏已成功数据。
|
|
||||||
6. **可观测性**:监控新写入成功但旧读模型缺失、投影延迟、去重冲突和回填差异。
|
|
||||||
|
|
||||||
### 5. 用户可见性是必须验收项
|
|
||||||
|
|
||||||
测试不得只证明“新写路径成功”。至少要有一个真实业务记录贯穿测试,同时证明:
|
|
||||||
|
|
||||||
- [ ] 新写路径产生了正确事实。
|
|
||||||
- [ ] 原有对外 API 可以立即或在约定 SLA 内返回该记录。
|
|
||||||
- [ ] 每个真实消费页面/客户端都能正确解码并展示。
|
|
||||||
- [ ] 时间、方向、状态、数量、金额与唯一标识在数据库、API 和 UI 之间一致。
|
|
||||||
- [ ] 部分失败、重试、重复投递、迟到数据和回滚后仍不丢数、不重复、不冒充。
|
|
||||||
|
|
||||||
开发计划必须把上述内容落成明确的变更文件、任务、测试用例和发布门禁,不得只在“风险”章节留一句提醒。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 开发计划文档模板
|
## 开发计划文档模板
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
@@ -1748,4 +1695,3 @@ migrate -path migrations -database "postgres://..." up
|
|||||||
|------|------|----------|--------|
|
|------|------|----------|--------|
|
||||||
| V1.0 | 2026-01-26 | 初始版本,创建 req-dev 技能 | Claude + qiudl |
|
| V1.0 | 2026-01-26 | 初始版本,创建 req-dev 技能 | Claude + qiudl |
|
||||||
| V1.1 | 2026-01-29 | 添加 iOS 端开发规范(SwiftUI + MVVM 架构) | Claude Opus 4.5 |
|
| V1.1 | 2026-01-29 | 添加 iOS 端开发规范(SwiftUI + MVVM 架构) | Claude Opus 4.5 |
|
||||||
| V1.2 | 2026-08-26 | 新增既有契约与读模型迁移强制门禁,防止新写链路与原 API/前端脱节 | Codex |
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "req-plugin",
|
"name": "req-plugin",
|
||||||
"description": "需求工作流管理入口。用于 /req 命令使用、需求生命周期管理、需求撰写(含草稿门禁)。",
|
"description": "需求工作流管理入口。用于 /req 命令使用、需求生命周期管理、需求撰写(含草稿门禁)。",
|
||||||
"version": "2.1.0",
|
"version": "2.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ analysis → design → dev → review → testing → [待部署池] → releas
|
|||||||
- **操作前先确认实际 ID** — 从 URL 提取 ID(如 `/requirements/897` → ID=897)
|
- **操作前先确认实际 ID** — 从 URL 提取 ID(如 `/requirements/897` → ID=897)
|
||||||
- **务实路线关闭也必须补全关联任务** — 每个进度条阶段需创建关联任务
|
- **务实路线关闭也必须补全关联任务** — 每个进度条阶段需创建关联任务
|
||||||
- **阶段内容门禁(防空转)** — `/req next` 时检查关键阶段任务是否有实质内容
|
- **阶段内容门禁(防空转)** — `/req next` 时检查关键阶段任务是否有实质内容
|
||||||
- **复用盘点先于估工(Gate 5,强制)** — PRD 必须含「现有实现与复用盘点」章节:每个能力点给出**现有实现(file:line)+ 结论(复用 / 改造 / 新增)**;结论=复用/改造的只按「接入差异」估工,结论=新增的必须写「为什么现有实现不能满足」。没有 file:line 证据、或标「未排查」的,**禁止 submit、禁止按全新增估工**;独立复查由非 PRD 写作者用 grep 复核并在 note 留命令与命中结果
|
|
||||||
- **Harness 环境检测** — `/req dev` 启动开发前,快速检测项目基础设施(`.husky/` 存在?`scripts/check-*.sh` 存在?)。若两者都不存在(Level 0),输出一行提示:「项目无质量护栏,建议先运行 `/harness init`」。检测结果不阻塞开发,仅提示
|
- **Harness 环境检测** — `/req dev` 启动开发前,快速检测项目基础设施(`.husky/` 存在?`scripts/check-*.sh` 存在?)。若两者都不存在(Level 0),输出一行提示:「项目无质量护栏,建议先运行 `/harness init`」。检测结果不阻塞开发,仅提示
|
||||||
|
|
||||||
## 禁止直接调用(必须走命令流程)
|
## 禁止直接调用(必须走命令流程)
|
||||||
@@ -56,7 +55,7 @@ analysis → design → dev → review → testing → [待部署池] → releas
|
|||||||
| 禁止直接调用 | 必须通过 | 原因 |
|
| 禁止直接调用 | 必须通过 | 原因 |
|
||||||
|-------------|---------|------|
|
|-------------|---------|------|
|
||||||
| `ai-proj req create` with allowSelfApprove | `/req new` | 禁止自动提交,必须创建为 draft |
|
| `ai-proj req create` with allowSelfApprove | `/req new` | 禁止自动提交,必须创建为 draft |
|
||||||
| `ai-proj req submit` (直接调用) | `/req submit` | 必须先通过 5 道门禁检查 |
|
| `ai-proj req submit` (直接调用) | `/req submit` | 必须先通过 4 道门禁检查 |
|
||||||
| `ai-proj req archive` | `/req done` | 必须先执行类型化门禁检查 |
|
| `ai-proj req archive` | `/req done` | 必须先执行类型化门禁检查 |
|
||||||
| `ai-proj req approve` | `/req review pass` | 必须先展示 PRD 摘要并等待用户确认 |
|
| `ai-proj req approve` | `/req review pass` | 必须先展示 PRD 摘要并等待用户确认 |
|
||||||
| `ai-proj req advance --to <stage>` (force) | `/req next` | 必须先 AskUserQuestion 确认 + 记录跳过原因 |
|
| `ai-proj req advance --to <stage>` (force) | `/req next` | 必须先 AskUserQuestion 确认 + 记录跳过原因 |
|
||||||
@@ -74,15 +73,13 @@ ai-proj req create \
|
|||||||
# → 结果必定是 draft 状态
|
# → 结果必定是 draft 状态
|
||||||
```
|
```
|
||||||
|
|
||||||
### 从 draft 到 submit 的 5 道门禁
|
### 从 draft 到 submit 的 4 道门禁
|
||||||
|
|
||||||
```
|
```
|
||||||
Gate 1: 需求讨论 ── description 末尾有「## 讨论结论」章节
|
Gate 1: 需求讨论 ── description 末尾有「## 讨论结论」章节
|
||||||
Gate 2: 方案确认 ── AskUserQuestion 获得「方案确认通过」
|
Gate 2: 方案确认 ── AskUserQuestion 获得「方案确认通过」
|
||||||
Gate 3: PRD 文档 ── linkRole=prd 的任务存在且有文档
|
Gate 3: PRD 文档 ── linkRole=prd 的任务存在且有文档
|
||||||
Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD 存在
|
Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD 存在
|
||||||
Gate 5: 复用盘点 ── PRD 有「现有实现与复用盘点」章节;每条能力点有 file:line 证据
|
|
||||||
或「为什么不能复用」的理由;估工按 复用/改造/新增 分别计(无盘点 = 阻断)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 完整工作流
|
## 完整工作流
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "req-prd-plugin",
|
"name": "req-prd-plugin",
|
||||||
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
|
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
|
||||||
"version": "2.3.0",
|
"version": "2.2.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -137,21 +137,6 @@ description: 产品设计与需求管理。用于 PRD、需求分析、用户故
|
|||||||
- 会议/邮件/聊天记录链接
|
- 会议/邮件/聊天记录链接
|
||||||
- 客户特殊约束(如"必须在 Q2 前上线")
|
- 客户特殊约束(如"必须在 Q2 前上线")
|
||||||
|
|
||||||
### 1.5 现有实现与复用盘点 ⭐ 强制
|
|
||||||
|
|
||||||
> **目的**:防止把「已经有的能力」当成新增来估工和开发(真实事故:DS Bot 早已有头像上传/裁剪功能,PRD 仍按新建估工)。
|
|
||||||
> **检索证据**:每条都要给 `file:line`(或 grep 命令 + 命中结果),不接受「凭印象认为没有」。
|
|
||||||
|
|
||||||
| 能力点 | 现有实现(file:line) | 结论 | 说明 |
|
|
||||||
|--------|----------------------|------|------|
|
|
||||||
| 头像上传/裁剪 | `ds-bot/src/client/bot-settings.js:xx` | 复用 | 只改存储落点与主体 |
|
|
||||||
| …(本次涉及的每个能力点都要有一行) | | | |
|
|
||||||
|
|
||||||
**硬规则**:
|
|
||||||
- 结论是 **复用 / 改造** 的,估工只算「接入差异」,不许按全新功能算。
|
|
||||||
- 结论是 **新增** 的,必须写「为什么现有实现不能满足」。
|
|
||||||
- 标「未排查」或没有 `file:line` 的 PRD **不得提交评审**;独立复查人(非 PRD 作者)要用 grep 复核并在 note 留证据。
|
|
||||||
|
|
||||||
## 2. 用户分析
|
## 2. 用户分析
|
||||||
### 2.1 目标用户
|
### 2.1 目标用户
|
||||||
[用户画像描述]
|
[用户画像描述]
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ description: Installer fixture version two.
|
|||||||
---
|
---
|
||||||
version two
|
version two
|
||||||
EOF
|
EOF
|
||||||
# Reproduce rsync's quick-check edge case: changed content with identical size
|
|
||||||
# and mtime must still replace the recorded, unmodified installation.
|
|
||||||
touch -r "$TEST_HOME/.agents/skills/example/SKILL.md" "$FIXTURE_REPO/skills-dev/example-plugin/skills/SKILL.md"
|
|
||||||
HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null
|
HOME="$TEST_HOME" "$FIXTURE_REPO/install-skills.sh" >/dev/null
|
||||||
grep -q 'version two' "$TEST_HOME/.agents/skills/example/SKILL.md"
|
grep -q 'version two' "$TEST_HOME/.agents/skills/example/SKILL.md"
|
||||||
grep -q '"version": "2.0.0"' "$TEST_HOME/.agents/.ai-proj-helper-installed-skills.json"
|
grep -q '"version": "2.0.0"' "$TEST_HOME/.agents/.ai-proj-helper-installed-skills.json"
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ PLUGIN_MAP = {
|
|||||||
"karpathy-guidelines-plugin": ("karpathy-guidelines", "skill", "dev"),
|
"karpathy-guidelines-plugin": ("karpathy-guidelines", "skill", "dev"),
|
||||||
"pull-request-plugin": ("pull-request", "skill", "dev"),
|
"pull-request-plugin": ("pull-request", "skill", "dev"),
|
||||||
"review-checklist-plugin": ("review-checklist", "skill", "dev"),
|
"review-checklist-plugin": ("review-checklist", "skill", "dev"),
|
||||||
"slark-dsh-cicd-plugin": ("slark-dsh-cicd", "skill", "dev"),
|
|
||||||
|
|
||||||
# skills-integration
|
# skills-integration
|
||||||
"data-excel-plugin": ("data-excel", "skill", "integration"),
|
"data-excel-plugin": ("data-excel", "skill", "integration"),
|
||||||
|
|||||||
Reference in New Issue
Block a user