c2638668e3
leo 2026-08-20:「同一個 plugin 你用,薄殼也用,保證兩邊同步」
「我要你幫雲端做薄殼,永遠都有問題,你要做的就是這組設定
你自己可以 dogfooding」
搬進來:41 支 hook(51 條註冊)/7 支 command/2 支 skill/23 支腳本。
不搬 .env、wiki、docs——那些是知識不是環境。
51 條 hook 路徑全部從 $CLAUDE_PROJECT_DIR/.claude/hooks/ 改成 ${CLAUDE_PLUGIN_ROOT}/hooks/,
零漏網。那正是薄殼一直壞掉的根:雲端 cwd 不是真身,寫死路徑就斷。
尚未驗證:Claude Code 能不能從私有 Gitea repo 裝 marketplace(要憑證)。
下一步就是在本機實際裝一次,通了才動雲端 bootstrap.sh。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
596 lines
33 KiB
Markdown
596 lines
33 KiB
Markdown
---
|
||
name: ship-check
|
||
description: |
|
||
改完任何會影響用戶的東西之後、說「做完了」之前必讀(改雲端 worker/portal/daemon/
|
||
workflow/installer 都算)。也在下列時機自動載入:要打包 App、要出貨、要推 bundle、
|
||
要送 MS Store、leo 問「可以測了嗎」「版本為什麼沒變」「更新了嗎」「封測者拿得到嗎」。
|
||
核心判準:**版本號是 leo 唯一的驗收介面**——portal 版本卡看雲端、daemon 檢查更新看桌面;
|
||
版本沒動=他無從判斷你做了什麼=等於沒交付,而「我在某台實例 wrangler deploy 過了」不算。
|
||
收齊:兩條版本線的差別/重打 bundle(最常漏,要 grep 複驗改動真的進去)/
|
||
改 workflow 要重編預編圖/三支機械閘+把 DMG/zip 真的打開檢查/寫 changelog(用戶語言)/
|
||
D20 開閘出貨/purge jsDelivr/從 leo 會看的那兩處抓實際畫面複驗。
|
||
附「常見漏掉的」實撞表與收工前五問。
|
||
---
|
||
|
||
# /ship-check — 改完東西後,讓 leo 看得到版本變了
|
||
|
||
> **這支解什麼病**(leo 2026-08-05 原話):
|
||
> 「對人來說,**我雲端看 portal 有沒有更新,本地看 daemon 有沒有更新**,
|
||
> 這個更新機制都寫好了,然後你說你改了這麼多居然版本一樣,還要去找怎麼做,
|
||
> **這個太危險了**。」
|
||
>
|
||
> 🔴 **核心判準:版本號是 leo 唯一的驗收介面。**
|
||
> 版本沒動 = 他無從判斷你做了什麼 = 你等於沒交付。
|
||
> 「我在某台實例 `wrangler deploy` 過了」**不算**——那只改了那一台。
|
||
|
||
---
|
||
|
||
## ⚠️ 這支 skill 自己的失效模式(先讀這段)
|
||
|
||
leo 2026-08-05:「**你寫完一個 skill 然後每個我要提醒你,表示這個 skill 無效**」
|
||
|
||
**根因**:我寫這支時是**憑印象列步驟**,沒有真的走一遍使用者的路
|
||
⇒ 於是「DMG 打開長什麼樣」「Info.plist 版本對不對」這些**只有真的打開才看得到**的東西全漏了,
|
||
每一條都要 leo 問「你檢查了嗎」才補。
|
||
|
||
**因此本 skill 的鐵律**:
|
||
1. **每一條檢查都要有可貼的實測輸出**——寫不出指令的條目就是還沒想清楚,不要列。
|
||
2. **凡是「使用者會看到的東西」,一律真的打開來看**(掛載 DMG、解開 zip、抓網頁內容),
|
||
不是確認檔案存在、不是看腳本說成功。
|
||
3. **被 leo 問出來的缺口,當場補進這支 skill**——否則下次還是靠他記得。
|
||
(本檔的 3.5、2.9 兩段都是這樣補進來的,日期都記著。)
|
||
|
||
---
|
||
|
||
## 什麼時候跑
|
||
|
||
**改完任何會影響用戶的東西之後**(雲端 worker/portal/daemon/workflow),
|
||
在說「做完了」之前。不是收工才跑。
|
||
|
||
---
|
||
|
||
## 🚚 雲端線出貨=**一個指令**,不要照下面的步驟手工做(2026-08-08 起)
|
||
|
||
> leo 2026-08-08:「**每次做一樣的事,結果會打錯實例,就是你的出貨閘是錯的。
|
||
> 寫對的應該每次都機械式的做同一件事,寫錯位置也會被它修正。**」
|
||
> 「這個出貨閘門就是廢的,它應該要像 GitHub Actions 一樣 CI/CD。」
|
||
|
||
```bash
|
||
cd products/arcrun-rag
|
||
node installer/scripts/ship.mjs --list # 有哪些目標
|
||
node installer/scripts/ship.mjs --target stage # 預演:建+算版本+報線上差距
|
||
node installer/scripts/ship.mjs --target stage --confirm # 真的走完 stage
|
||
node installer/scripts/ship.mjs --target prod --confirm # prod(先 stage,且需 leo 開閘)
|
||
```
|
||
|
||
**九個步驟固定不變**:`preflight → build → version → commit → push → pin → deploy → purge → verify`
|
||
每一步只有「執行/跳過(附機械理由)」兩種結果,**斷了後面一步都不跑**。
|
||
|
||
它已經替你做掉的事(**所以下面 §2〜§6 的手工步驟不要再照著做一遍**):
|
||
|
||
| 以前靠人記得 | 現在 |
|
||
|---|---|
|
||
| 記得重打 bundle | `build` 步驟**每次都重打**(含 `build-ui-bundle.mjs`——它跟 `build-bundles.mjs` 是**兩支**,只跑一支就是 portal 改動送不出去) |
|
||
| 記得換釘子、且**兩處都要換** | `pin` 步驟同時寫 `wrangler.toml` 的 vars 與 `worker.js` 常數 |
|
||
| 記得 purge jsDelivr | `purge` 步驟(prod 才需要),驗到收斂為止 |
|
||
| 記得複驗線上 | `verify` 步驟**真的把 daemon 下載下來算 sha256** |
|
||
| 手打 `--bundles <路徑>`(會打錯) | 🔴 **已移除**。目標只能 `--target`,座標全來自 `installer/ship.targets.json` |
|
||
|
||
🔴 **目標打錯打不進去**:本機 clone 的 `origin` 與登錄簿不符 ⇒ 當場擋;
|
||
`CLOUDFLARE_ACCOUNT_ID` 一律由登錄簿覆蓋,不吃環境裡飄來的值;
|
||
`prod` 沒有 leo 的 `.github-armed` ⇒ preflight 就斷。
|
||
|
||
📌 **stage 驗證章由管線自己蓋**(`--target stage --confirm` 成功才寫 `/tmp/.stage-verified`)。
|
||
**不要用手 `touch`**——手蓋的章證明不了任何事,那正是這道閘想擋的東西。
|
||
|
||
⚠️ 桌面線(打包 App/DMG/exe)**還沒併進管線**,仍照下面 §3、§3.5 手工做,
|
||
做完把產物放進 bundles repo 的 `daemon/`,再跑 `ship.mjs`(它會驗版本與 sha 對不對得上)。
|
||
|
||
---
|
||
|
||
## 兩條版本線(先分清楚在講哪一條)
|
||
|
||
| 線 | 版本號 | leo 從哪看 | 出貨鏈 |
|
||
|---|---|---|---|
|
||
| **雲端** | `manifest.release`(如 `1.4.11`) | **portal 設定頁的版本卡** | 重打 bundle → 推 bundles repo → release 自動 bump → 換安裝器釘子 → 部署安裝器 |
|
||
| **桌面** | `manifest.daemon.version`(如 `v0.18.4`) | **daemon 的「檢查更新」** | 打包 App → 放進 bundles `daemon/` → 改 `manifest.daemon` → purge jsDelivr |
|
||
|
||
⚠️ 兩條**各自獨立**。改 kbdb 不會讓 daemon 版本動,反之亦然。
|
||
|
||
---
|
||
|
||
## 步驟
|
||
|
||
### 0. 先查記憶(別重蹈覆轍)
|
||
|
||
```bash
|
||
grep -rn "版本號\|出貨\|release" system-dev/wiki/decisions-summary.md | head
|
||
```
|
||
|
||
必讀 **D39「版本號由內容算出來,不由人宣告」**:
|
||
- `MAJOR.MINOR` 在 `RELEASE_LINE` 檔(人只在大改版動)
|
||
- **`PATCH` 由機器決定**:內容指紋一變 +1、沒變不動(重跑不虛增)
|
||
- **真相源只有 `manifest.release` 一處**,installer/landing/portal 全是讀者
|
||
|
||
> ⇒ **「改了 code 但 release 沒 bump」= bundle 沒重打包**,不是版本機制壞了。
|
||
|
||
### 1. 判斷這次改動影響哪條線
|
||
|
||
```bash
|
||
git -C matrix/arcrun log --oneline -5 # 雲端 worker(cypher/kbdb/portal…)
|
||
git -C products/arcrun-rag log --oneline -5 # daemon/安裝器/workflow
|
||
```
|
||
|
||
- 動到 `matrix/arcrun` 的 worker 或 `console-ui/public/portal/` → **雲端線**
|
||
- 動到 `collector/`(含 `cmd/arcrun-app/`) → **桌面線**
|
||
- 動到 `workflows/*.yaml` → **要重編 workflows.json**(見步驟 2.5)
|
||
|
||
### 2. 雲端線:重打 bundle(**這步最常漏**)
|
||
|
||
```bash
|
||
cd products/arcrun-rag
|
||
ARCRUN_REPO_ROOT=../../matrix/arcrun node installer/scripts/build-bundles.mjs --out <bundles repo>/
|
||
```
|
||
|
||
**複驗你的改動真的進去了**(不要只看腳本說成功):
|
||
|
||
```bash
|
||
grep -c "<你改的關鍵字>" <bundles repo>/tier2/<worker>/index.js
|
||
# 例:改 embed 模型 → grep -c "bge-m3" 應 > 0,且舊模型應為 0
|
||
```
|
||
|
||
> 🔴 實撞(2026-08-05):我改了 kbdb 的 embed 模型並 `wrangler deploy` 到 youlin,
|
||
> 但**沒重打 bundle** ⇒ bundles repo 裡仍是 `bge-base-en`
|
||
> ⇒ **新用戶安裝/既有用戶重裝都拿到舊的**,而 leo 看到的 release 仍是 1.4.11。
|
||
|
||
### ⛔ 2.9 bundle 沒重打之前,**要主動叫 leo 別按「立即更新」**
|
||
|
||
**這是最危險的狀態**,比「沒出貨」更糟:
|
||
|
||
- 你直接 `wrangler deploy` 到某台實例 ⇒ **那台**是新的
|
||
- 但 bundle 還是舊的 ⇒ leo 按 portal 的「**立即更新**」=**從舊 bundle 重裝**
|
||
- ⇒ **他的實例會被你剛修好的東西「降級」回舊版**
|
||
|
||
實例(2026-08-05,leo 說「我要去更新實例驗證」時攔下):
|
||
kbdb 已直推 `bge-m3`(1024 維)+已建 1024 維 Vectorize index,
|
||
但 bundle 裡仍是 `bge-base-en`(768) ⇒ 一按更新,kbdb 退回舊模型,
|
||
**與 1024 維 index 對不上 ⇒ 中文語意搜尋直接壞掉**。
|
||
|
||
📌 **判準**:只要你「直推過實例」但「還沒重打 bundle」,
|
||
**主動說一句「先別按立即更新,會裝回舊的」**——不要等 leo 自己踩到。
|
||
兩者狀態不一致的期間,**降級風險是你造成的,說清楚是你的責任**。
|
||
|
||
### 🔁 2.7 動到安裝/更新/認證的話:**stage 要走三段,不准只測 update**
|
||
|
||
(leo 2026-08-14 立,全文見頂層 `decisions-summary.md` **D84 之二**)
|
||
|
||
> leo 原話:「**以後你的 stage 測試除了測 update 還要用 uninstaller 刪除後再模擬第一次安裝。**」
|
||
|
||
```
|
||
① 測 update(既有 stage 實例)
|
||
② 用 uninstaller 拆掉
|
||
③ 從乾淨狀態模擬第一次安裝
|
||
```
|
||
|
||
🔴 **只做 ① 不算測過。** stage 實例是長期存在的,它身上有所有舊 binding 與舊 secret
|
||
⇒ **它天生就是「既有實例」,天生看不到全新用戶會撞的坑。**
|
||
|
||
**這條的代價是實際發生過的**(2026-08-14 一天三個坑,全是只驗 update 會漏掉的):
|
||
- 安裝器從沒種過 `CF_SECRETS_API_TOKEN` ⇒ **全新用戶建不出第一個帳號**(`Arcrun#119`)
|
||
- 中心 KV 說「你裝過了」而帳號上什麼都沒有 ⇒ **重裝死結**(`Arcrun#120`)
|
||
- 撞牆訊息叫用戶去跑一件做不到的事(`Arcrun#121`)
|
||
|
||
**每一個都在既有實例上測不出來**,因為既有實例會沿用舊資源把缺口蓋住。
|
||
|
||
⚠️ **uninstaller 還沒做出來之前**,這條走不完 ⇒ 出貨時要**明講「③ 沒驗」**,
|
||
不准因為「工具還沒有」就跳過不提。
|
||
|
||
### 2.5 改過 workflow 的話
|
||
|
||
```bash
|
||
CYPHER_BASE=<實例 cypher URL> CYPHER_NS=<namespace> \
|
||
node installer/scripts/compile-workflows.mjs
|
||
```
|
||
|
||
⚠️ **flow 變了就不能沿用舊的預編圖**,腳本會 `exit 1` 擋住(這個閘是對的)。
|
||
不帶 `CYPHER_BASE` 就跑=直接失敗,別繞過它。
|
||
|
||
### 3. 桌面線:打包 App
|
||
|
||
```bash
|
||
cd products/arcrun-rag/collector/cmd/arcrun-app
|
||
VERSION=vX.Y.Z bash build-mac.sh && bash build-dmg.sh # Mac
|
||
VERSION=vX.Y.Z bash build-win.sh # Windows(Mac 上可交叉編譯)
|
||
```
|
||
|
||
**三支機械閘必須全過**(交貨前):
|
||
|
||
```bash
|
||
for s in check-cis.sh check-render.sh check-tray.sh; do bash "$s" || echo "❌ $s"; done
|
||
```
|
||
|
||
**實跑驗證產物**(不是看檔案存在):Mac 用 `open` 真的跑一次;
|
||
或用同綑的 collector 跑 `direct --once` 確認你的修復在裡面。
|
||
|
||
#### 3.5 🔴 **把 DMG/zip 真的打開,用「使用者第一次看到的樣子」檢查**
|
||
|
||
> leo 2026-08-05 連問三次才問出來的東西——**列步驟不算驗,要真的開起來看**。
|
||
> 「我問你 Mac 打包是不是一個 folder 打開可以把 dmg 拖到 application 去?**你檢查了嗎?**」
|
||
|
||
```bash
|
||
# Mac:掛載 DMG,看使用者會看到什麼
|
||
hdiutil attach dist/Arcrun-vX.Y.Z.dmg -nobrowse -quiet -mountpoint /tmp/dmgchk
|
||
ls /tmp/dmgchk/ # 應**剛好兩項**:Arcrun.app + Applications 捷徑
|
||
ls /tmp/dmgchk/Arcrun.app/Contents/MacOS/ # ⚠️ 2026-08-14 訂正:現在是**單一二進位**,只有 arcrun-app
|
||
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" \
|
||
/tmp/dmgchk/Arcrun.app/Contents/Info.plist # ← 必須是本次版本,**不是 1.0.0**
|
||
/usr/libexec/PlistBuddy -c "Print :LSUIElement" \
|
||
/tmp/dmgchk/Arcrun.app/Contents/Info.plist # 應為 true(不佔 Dock)
|
||
codesign -v /tmp/dmgchk/Arcrun.app # 無輸出=簽章有效
|
||
hdiutil detach /tmp/dmgchk -quiet
|
||
|
||
# Windows:解開 zip 看內容
|
||
unzip -l dist/ArcrunRAG-win-unsigned-vX.Y.Z.zip # 應有 Arcrun.exe + arcrun-collector.exe
|
||
```
|
||
|
||
**逐項判準**:
|
||
| 檢查 | 為什麼 | 漏掉的後果 |
|
||
|---|---|---|
|
||
| DMG 內剛好兩項 | 這就是「拖進 Applications」的標準畫面 | 沒有捷徑 ⇒ 使用者在下載資料夾直接開 ⇒ **更新會蓋錯位置**(t184) |
|
||
| ~~`arcrun-collector` 同綑~~ **已過期(2026-08-14 訂正)** | 以前 daemon 是獨立二進位;**現在 collector 內建在 `arcrun-app` 裡**(v0.18.27 實測:`strings` 抓得到 `LoadDirectConfig`/`saveDirectConfig`,direct 模式符號 18 處) | 🔴 **照舊版檢查會發出「你的 App 缺件」的假警報**——總管 08-14 差點對 leo 發出來。要驗「同步能力在不在」改用 `strings` 抓 direct 模式符號 |
|
||
| `CFBundleShortVersionString` | Finder/「關於」顯示的版本 | **永遠顯示 1.0.0** ⇒ 用戶無從判斷自己是不是新版 |
|
||
| `LSUIElement=true` | 常駐小工具不該佔 Dock | 行為與設計不符 |
|
||
| `codesign -v` | 改過 bundle 內容必重簽 | launchd 拒開 |
|
||
|
||
> 🔴 實撞(2026-08-05):`CFBundleShortVersionString` 一直是 **`1.0.0`**——
|
||
> `wails.json` 沒有 `info.productVersion`,而 `wails build` **沒有 CLI 旗標**可指定。
|
||
> 解法已寫進 `build-mac.sh`/`build-win.sh`:建置前把版本寫進 `wails.json` 的 `info`、
|
||
> 建完用 `trap` 還原(不污染版控)。
|
||
|
||
### 4. 寫 changelog(**leo 點名要的**)
|
||
|
||
`products/arcrun-rag/docs-site/src/content/docs/help/changelog.md`
|
||
|
||
格式照既有的(**寫給用戶看,不是寫 commit message**):
|
||
|
||
```markdown
|
||
|
||
## 📋 出貨對照表:**改了什麼 → 要動什麼**(leo 2026-08-08:「寫清楚不要再改錯」)
|
||
|
||
> 這張表是**查的**,不是用判斷的。08-08 一整天的錯全是「憑印象決定要重打什麼」。
|
||
> **每次出貨逐列對,有碰到就一定要做。**
|
||
|
||
| 你改了什麼 | 要重打/重部署 | 版本線 | 要一起改的文件 | 最後落在哪 |
|
||
|---|---|---|---|---|
|
||
| **daemon Go 程式** | 三平台打包(mac/win/msix) | **daemon 版本線** | `changelog.md` **必寫** | 用戶自己的電腦 |
|
||
| **portal/console-ui 前端** | `build-ui-bundle.mjs` | **雲端 release** | 有畫面變化就要更 docs | 每個實例的 `arcrun-rag-ui` |
|
||
| **cypher / kbdb / registry / mcp** | `build-bundles.mjs` | **雲端 release** | — | 每個實例的對應 worker |
|
||
| **安裝器 `worker.js`** | 部署安裝器(帶 `--config`) | 安裝器自己 | — | `install.arcrun.dev` |
|
||
| **landing** | 部署 landing | — | — | `rag.arcrun.dev` |
|
||
| **說明文件** | `npm run build` → **`dist`→`deploy/docs`** → 部署 | — | — | `rag.arcrun.dev/docs` |
|
||
| **錯誤分類/新的失敗原因** | — | — | 🔴 **FAQ 一定要同步** | docs 站 |
|
||
|
||
🔴 **兩個最常漏的(都有 08-08 實據)**:
|
||
- `build-bundles.mjs` **不會重建 `arcrun-rag-ui`**(它在「沿用非本腳本產」清單裡)
|
||
⇒ 只跑一支=**靜默出半套**。portal 文案傳不出去整整一天就是這個。
|
||
- docs 的 `dist → deploy/docs` **沒有腳本**,漏了它 `wrangler deploy` 會「成功」但只上傳 0.35 KiB。
|
||
|
||
### 版本修改內容(`changelog.md`)怎麼寫
|
||
|
||
`changelog.md` 是**版本號的單一真相源**,也是使用者在「版本與更新」畫面看到的內容。
|
||
|
||
- **一行一句,講「你會看到什麼不一樣」**,不是 commit 訊息翻譯
|
||
- 🔴 **要短。細節去 docs 讀**(leo 08-08 看到 v0.18.24 的更新說明是一整面文字牆:
|
||
「**不要這麼長的散文,簡短講改了什麼,細節去 docs 讀**」)
|
||
- **不要帶 markdown 粗體**——那個欄位是純文字,`**` 會原樣顯示給使用者看
|
||
- 沒有對應版本的段落就**不准打包**(`changelog-section.sh --check` 會擋)
|
||
|
||
### FAQ 什麼時候一定要動
|
||
|
||
- **新增/改變任何「使用者會看到的失敗原因分類」** ⇒ FAQ 必須同步
|
||
(分類收斂成有限幾類,畫面只出分類與份數,**細節全在 FAQ**——見 t214)
|
||
- **改了安裝或更新的做法** ⇒ 對應的說明頁必須同步
|
||
(08-08 實據:Windows 說明還寫「解壓縮 zip、裡面兩個 exe」,而我們早就只出單一 `.exe`)
|
||
|
||
📌 **判準:使用者會因為這次改動而問出新問題嗎?會 → FAQ 要有答案。**
|
||
|
||
### 什麼地方 → 修哪個實例(別再搞混)
|
||
|
||
> 🔴 **AI 的工作迴圈只有兩件事**(leo 2026-08-08 原話):
|
||
> 「**實際上你是在 uncle6 修改 bundle,然後你自己在 youlin stage 安裝新版 bundle。**」
|
||
> ⇒ **改 staging bundle(uncle6)→ 裝進 youlin 的 stage 環境 → 自己測**。這一圈可以無限重複。
|
||
> **「uncle6 不碰」指的是 prod 那一側,不是 staging bundle**——別把自己的工作區也擋掉。
|
||
|
||
| 這個東西 | 住在誰的帳號 | AI 能不能動 |
|
||
|---|---|---|
|
||
| **staging bundle repo** | uncle6(Gitea) | ✅ **這是 AI 的工作區**,隨便改隨便推 |
|
||
| **youlin 的 stage 環境** | youlin | ✅ **AI 的測試場**,隨便裝、壞了自己修 |
|
||
| **prod bundle repo** | uncle6(GitHub) | 🔴 要 leo arm,且只准「**原封提升**」不准重打 |
|
||
| **prod 的安裝器/landing/docs 部署** | uncle6 | 🔴 部署=出貨動作,要 leo 開閘 |
|
||
| **youlin 的 prod 環境** | youlin | 👤 leo 用來驗「用戶裝 prod 拿到什麼」 |
|
||
| **geek6688(出貨機/第一個顧客)** | geek6688 | 🔴 **不准拿它當測試場**;但**發佈後總管要手動把它更新到最新**——見 `ops-facts.md` §出貨線。**出貨機比貨舊是錯的**(08-12 實撞:停在 1.4.29、落後 12 版) |
|
||
| `rag-demo.arcrun.dev` | uncle6 | 🪦 **已退場**,任何文件/腳本不得再導向 |
|
||
|
||
📌 **一句話界線**:**staging 側全是我的、prod 側全是 leo 的閘。**
|
||
|
||
### 🖥️ 三台機器各自幹嘛(leo 2026-08-12 定義,08-14 再次口頭確認)
|
||
|
||
> **youlin =測試機|geek6688 =出貨機|uncle6 =總部機**
|
||
|
||
- **youlin =測試場**:**它就是拿來拆的**。裝壞了、要清空重來,**不必問、不必猶豫**
|
||
——把它當成珍貴資產而不敢動,等於讓「只有全新用戶會撞的坑」永遠測不到(D84 之二)。
|
||
唯一要守的是**時序**:確認沒有別條線正踩在上面。
|
||
- **geek6688 =出貨機/第一個顧客**:**不當測試場**,但**發佈後要更新到最新**(總管手動)。
|
||
- **uncle6 =總部機**:bundle 的家。staging 側是總管的工作區,**prod 側是 leo 的閘**。
|
||
|
||
📌 **總管 2026-08-14 實錯**:leo 交辦拆 youlin 時,總管去問「有沒有資料是重裝也長不回來的」
|
||
——**而這個答案 `ops-facts.md:29` 早就寫著了**。leo 當場點破:「**stage 不就是用來測的?**」
|
||
⇒ **對測試機的謹慎不是美德,是把該做的驗證擋在門外。**
|
||
|
||
## 🗺️ 出貨流程(leo 2026-08-08 深夜定死,不得再自創路徑)
|
||
|
||
> leo 原話:「**uncle6 的 bundle 要裝在 youlin 的 stage 環境,沒問題後才推 prod 改裝在
|
||
> youlin 的 prod 環境,所以 uncle6 的 bundle 要 2 套,youlin 也要兩套,
|
||
> 就像把同樣程式碼打到不同分支一樣。**」
|
||
> 「**CF 可以這樣建立分離的 stage,不是在 prod 環境測 stage。**」
|
||
|
||
### 矩陣(兩軸,四格,缺一格就會像 08-08 那樣炸)
|
||
|
||
```
|
||
產物來源(uncle6) 安裝目標(youlin)
|
||
stage → staging bundle repo → youlin 的 stage 環境
|
||
prod → prod bundle repo → youlin 的 prod 環境
|
||
```
|
||
**同一份程式碼打到兩條線,像 git 的兩個分支——彼此不共用任何會互相污染的東西。**
|
||
|
||
### 六步,順序不准跳
|
||
|
||
```
|
||
① 改 code → commit + push gitea
|
||
② 打 bundle → 版本號由內容指紋算出;內容變了版本一定變
|
||
⚠️ 要跑兩支:build-bundles.mjs + build-ui-bundle.mjs
|
||
(前者不會重建 arcrun-rag-ui,漏跑=靜默出半套)
|
||
③ 推 staging bundle → Gitea(不碰 GitHub,無 D20 閘)
|
||
④ 裝進 youlin 的 stage 環境 → AI 自己測:登入、首頁、匯出診斷檔(瀏覽器實載)
|
||
⑤ 👤 **leo 去 youlin 的 stage 環境**,看修復版**是不是真的修好了**
|
||
→ 他說可以,跑 scripts/stage-ok.sh
|
||
⑥ 👤 leo 跑 scripts/github-arm.sh
|
||
→ **把 staging bundle「原封提升」成 prod bundle**(uncle6 內部:staging repo → prod repo)
|
||
⑦ 👤 leo 在 **youlin 的 prod 環境**裝一次新版 prod 內容
|
||
→ 驗的是「**用戶從 prod 安裝器裝,會拿到什麼**」這條路本身
|
||
⑧ 之後任何人從 prod 安裝器裝就拿到新版;leo 想在哪抽查都行(geek6688 或任何實例)
|
||
```
|
||
|
||
🔴 **youlin 為什麼一定要兩套環境**(leo 2026-08-08:「**然後我可以在 youlin 測新版的
|
||
prod 內容安裝**」):
|
||
- **stage 環境** → 驗「修復到底修好了沒」(發佈前的閘,第 ⑤ 步)
|
||
- **prod 環境** → 驗「用戶裝 prod 會拿到什麼」(發佈後的第一手驗證,第 ⑦ 步)
|
||
⇒ 兩件事驗的東西不同,**不能共用同一套環境**——
|
||
共用就會變成 2026-08-08 那樣:為了測而動到正在用的那套,把要驗的東西弄壞。
|
||
|
||
🔴 **⑥ 是「提升」不是「重打」**(leo 2026-08-08 原話:
|
||
「**arm 推的是 uncle6 把 stage 的 bundle 推到 prod 的 bundle**」)
|
||
⇒ **prod 的產物必須與 stage 上被驗過的那一份逐位元相同**。
|
||
一旦重跑一次 build 才推 prod,**stage 驗過的東西就不是 prod 上的東西**,
|
||
⑤ 那一關的意義當場歸零。
|
||
⇒ 驗收方式:提升後比對兩邊的**指紋/sha**,不同就是做錯了。
|
||
這正是 leo 的比喻「**就像把同樣程式碼打到不同分支一樣**」——搬,不是再做一次。
|
||
|
||
🔴 **stage 永遠在 youlin,這是固定的**(leo 2026-08-08 原話:
|
||
「**5 是我去 youlin stage 環境看你的修復版是否真的修復,就可以發佈,發佈以後,
|
||
我可以在任意地方試 prod,但 stage 一定在 youlin。**」)
|
||
⇒ **核實點固定在 stage**,不是「找另一台乾淨的機器來驗」。
|
||
prod 發佈後在哪裡試都行,那是**發佈後的抽查**,不是發佈前的閘。
|
||
|
||
**兩把鑰匙都在 leo 手上(⑤⑥),AI 造不出來,這是刻意的。**
|
||
|
||
### CF 官方機制(查證出處,不是憑記憶)
|
||
|
||
`https://developers.cloudflare.com/workers/wrangler/environments/`
|
||
|
||
- 用 `[env.NAME]` 宣告環境;部署 `npx wrangler deploy --env NAME`
|
||
- **Worker 名字自動變成 `<top-level-name>-<env>`**(例:`arcrun-cypher-executor-stage`)
|
||
- 🔴 官方原文:**「Non-inheritable keys are configurable at the top-level, but cannot be
|
||
inherited by environments and must be specified for each environment.」**
|
||
⇒ **KV/D1/vars 一律不繼承,每個環境必須各自宣告**——這正是環境真正分離的地方,
|
||
也是「漏宣告就會共用到 prod 資源」的風險點。
|
||
|
||
### 🔴 為什麼要把流程寫死(08-08 一整天的代價)
|
||
|
||
那天出貨是**用手拼的**:手動 cp 產物 → 手動改 manifest → 手動 sed 換兩處釘子 → 手動 deploy。
|
||
後果四連發,全部有實據:
|
||
- 改了 portal 文案,**bundle 版本沒動** ⇒ 改動永遠送不出去
|
||
- 版本沒動 ⇒ 安裝器判「同版整批跳過」⇒ **重裝也修不好**(leo 白重裝一次)
|
||
- 兩台都宣稱 `1.4.22` **程式碼卻不同**(一台有診斷端點、一台 404)
|
||
- 為了測 stage 而手動部署,**繞過安裝器注入** ⇒ leo 的 portal 畫面壞、登入斷
|
||
⇒ **他晚上要驗收時,發現要驗的東西被驗收流程本身弄壞了**
|
||
|
||
⇒ leo:「**每次做一樣的事,結果會打錯實例⋯⋯寫對的應該每次都機械式的做同一件事,
|
||
寫錯位置也會被它修正。**」**這條流程就是那個「機械式」的定義,不准再自創。**
|
||
|
||
## 🚦 第 0 步:**先上 stage,不准直達 prod**(leo 2026-08-08 立,封測期起)
|
||
|
||
> leo:「現在因為**開始封測**,不直接打到 prod,而是先打到昨天建的 stage⋯⋯
|
||
> 因為**推 prod 就發佈了**,雖然現在人不多,但**要謹慎**。」
|
||
|
||
🔴 **這一段是補進來的,因為本 skill 原本從頭到尾寫的是 prod**
|
||
(github-arm → GitHub `arcrun-rag-bundles` → jsDelivr/raw 驗證),
|
||
**沒有任何一步是「先上 stage 驗過再上 prod」**;而 D20 那道閘擋的是「寫 GitHub」,
|
||
不是「未經 stage 就發佈」⇒ 光「知道有 stage」對行為零作用。
|
||
|
||
順序(**不准跳**):
|
||
|
||
1. 打 **staging** bundle → 推 Gitea `arcrun-rag-bundles-staging`
|
||
(不碰 GitHub,**無 D20 閘**,不需要 leo)
|
||
2. 用 staging 安裝器實裝到測試實例
|
||
`https://arcrun-rag-installer-staging.uncle6-me.workers.dev`
|
||
3. 走一次**封測者真的會走的那條路**,貼實測輸出(不是 HTTP 200)
|
||
4. 過了才做下面的 prod 步驟
|
||
|
||
⚠️ **身分**:leo 2026-07-25 令「測試一律用 youlin,別拿 leo21c 當探針(會製造假信號)」
|
||
⇒ 部署前先 `acr whoami`。
|
||
|
||
⚠️ **stage 與 prod 內容範圍不一樣**(daemon 安裝檔、README、core 顆數)
|
||
⇒ 見下面「別再整包蓋」那段,**不要拿 staging manifest 整包覆蓋 prod**。
|
||
|
||
📌 stage 判準(leo 08-07):stage **不需要 custom domain**,`*.workers.dev` 就好——
|
||
「僅預設網域不准標 ✅」的目的是「用戶拿不到=沒交付」,而 stage 的用戶就是 leo 與總管。
|
||
不掛 custom domain 反而是優點(沒人會誤入、不被索引、不會被當正式網址傳出去)。
|
||
|
||
🔧 機械閘:`.claude/hooks/stage-before-prod-guard.sh`
|
||
(6 小時內沒 stage 驗證紀錄 ⇒ 擋掉 prod bundle repo/github-arm/publish-github)。
|
||
|
||
📖 stage 救過一次的實例:見頂層 `system-dev/wiki/status.md`
|
||
「同一次測試照出一個會炸掉所有用戶的回歸(stage 第一次真的救了我們)」。
|
||
|
||
## vX.Y.Z(YYYY-MM-DD)
|
||
|
||
**建議更新**——一句話說「這版解決你什麼問題」。
|
||
|
||
- 🔴 **最重要那項**:用戶語言描述症狀與結果
|
||
- 其他項…
|
||
```
|
||
|
||
判準:**用戶讀得懂「這對我有什麼差別」**。
|
||
不要寫「修 t195 的 401」,要寫「修好『一個檔失敗就卡住整個資料夾』」。
|
||
|
||
### 5. 出貨(**需 leo 開 D20 閘**)
|
||
|
||
```bash
|
||
# leo 親跑(AI 不得代跑)
|
||
scripts/github-arm.sh "出貨 <說明>" 30
|
||
```
|
||
|
||
開閘後:
|
||
|
||
```bash
|
||
cd products/arcrun-rag
|
||
node installer/scripts/ship.mjs --bundles <bundles repo> # 先 dry-run 看待辦
|
||
node installer/scripts/ship.mjs --bundles <bundles repo> --confirm # 真出貨
|
||
```
|
||
|
||
`ship.mjs` 會依序做:推 bundle → 換安裝器釘子 → 部署安裝器/landing → **purge jsDelivr**。
|
||
|
||
⚠️ **purge 一次可能不夠**,要驗到收斂(腳本自己會重試,別提前宣稱完成)。
|
||
|
||
### 5.5 說明文件站(**2026-08-08 補:它不在任何腳本裡,最容易整批過期**)
|
||
|
||
🔴 **`ship.mjs` 完全不管 docs**(`grep docs` = 0 命中)。docs 是獨立的 `arcrun-docs` worker,
|
||
沒人手動部署它就會**一直停在上一次**——leo 2026-08-08 抓到:Windows 安裝說明還寫著
|
||
「解壓縮 zip、裡面有兩個 exe」和「MSIX 要開開發人員模式」,而**我們早就只出單一 `.exe`**。
|
||
|
||
```bash
|
||
cd docs-site
|
||
npm run build # → dist/
|
||
rm -rf deploy && mkdir -p deploy/docs && cp -R dist/. deploy/docs/ # ⚠️ 見下
|
||
npx wrangler deploy
|
||
```
|
||
|
||
⚠️ **`dist → deploy/docs` 這一步沒有腳本,只活在人的記憶裡**(08-08 實撞):
|
||
`wrangler.toml` 的 `[assets] directory = "./deploy"`,而 Astro 建到 `dist`。
|
||
**漏了它,`wrangler deploy` 會「成功」但只上傳 0.35 KiB(等於什麼都沒換)。**
|
||
|
||
**複驗要抓畫面內容,不是看部署訊息**:
|
||
```bash
|
||
curl -s "https://rag.arcrun.dev/docs/start/install-windows/?cb=$RANDOM" \
|
||
| grep -oE "<這次該出現的字串>"
|
||
```
|
||
|
||
📌 **判準:凡是改動會讓說明文件過期的出貨,docs 就是出貨的一部分。**
|
||
版本、安裝方式、畫面長相變了 ⇒ 一起改、一起部署、一起複驗。
|
||
|
||
### 5.8 🔴 驗前端=**用瀏覽器真的載一次**,`curl | grep` 不算(leo 2026-08-08 立)
|
||
|
||
> leo 原話:「**你的環境有 web,你應該用 web 驗,你已經開啓了卻沒有完成,你要把這個列入規定。**」
|
||
|
||
**為什麼 `curl | grep` 是假驗證**(08-08 實撞,leo 抓到而不是我發現):
|
||
`curl` 拿到的是 **HTML 原始碼**——它**不執行 JS、不載入 `config.js`、不發 API 請求**。
|
||
所以我 grep 到文案就宣稱「前端驗過」,而使用者實際打開看到的是整條紅色錯誤:
|
||
「設定檔沒載入(config.js),這個頁面連不到你的服務」。**那個畫面我一次都沒看到。**
|
||
|
||
⇒ **判準升級**:
|
||
`HTTP 200 不算驗過` → `grep 到字串也不算驗過` → **只有「瀏覽器載入後看起來能用」才算**。
|
||
|
||
```
|
||
mcp__Claude_Browser__preview_start {url: "<用戶會走的網址>"}
|
||
mcp__Claude_Browser__computer {action: "screenshot"} ← 看畫面,不是看原始碼
|
||
mcp__Claude_Browser__read_console_messages {onlyErrors: true} ← JS 有沒有炸
|
||
```
|
||
|
||
**要看的是**:有沒有錯誤橫幅/該有的資料是不是還停在「載入中…」「查詢中…」/console 有沒有紅字。
|
||
|
||
⚠️ **順帶一個會騙人的坑(同日實撞)**:`curl` 加 `?cb=$RANDOM` **繞不掉 Cloudflare 邊緣快取**——
|
||
`/config.js` 一直回舊的空值,直到加 `-H "Cache-Control: no-cache"` 才看到真值。
|
||
⇒ 用 curl 查線上狀態時,**沒加 no-cache 就可能是在驗快取,不是在驗線上**。
|
||
|
||
### 6. 複驗:**從 leo 會看的那兩個地方**
|
||
|
||
```bash
|
||
# ① 雲端線:portal 版本卡讀的是安裝器 /api/latest
|
||
curl -s https://install.arcrun.dev/api/latest
|
||
# → "release" 應等於 manifest.release
|
||
|
||
# ② 桌面線:daemon「檢查更新」讀的是 jsDelivr(**不是 raw**)
|
||
curl -s "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/manifest.json" \
|
||
| python3 -c "import json,sys; print(json.load(sys.stdin)['daemon']['version'])"
|
||
|
||
# ③ 用戶下載的固定檔名真的抓得到、且 sha 與 manifest 相符
|
||
curl -sI "https://raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/main/daemon/ArcrunRAG-mac.dmg"
|
||
```
|
||
|
||
🔴 **`raw` 是新的不代表 `jsDelivr` 是新的**——daemon 讀 jsDelivr,
|
||
2026-08-05 實撞:push 完 raw 立刻新版、jsDelivr 仍吐 v0.15.7,purge 後第 2 次才收斂。
|
||
|
||
---
|
||
|
||
## 收工前自問(答不出來就是還沒做完)
|
||
|
||
1. **leo 打開 portal,版本卡會顯示新號碼嗎?** 不會 → 雲端線沒出貨完
|
||
2. **leo 按 daemon「檢查更新」,會看到新版嗎?** 不會 → 桌面線沒出貨完
|
||
3. **新用戶現在安裝,拿到的是我改的那份嗎?** 不確定 → 回步驟 2 複驗 bundle 內容
|
||
4. **changelog 有這一版嗎?** 沒有 → 用戶不知道你改了什麼
|
||
5. 以上任一是「否」→ **不准說「完成」,要說「已改,未送達」**
|
||
|
||
---
|
||
|
||
## 常見漏掉的(實撞記錄)
|
||
|
||
| 漏掉的 | 後果 | 日期 |
|
||
|---|---|---|
|
||
| 沒重打 bundle | release 不 bump、新用戶拿舊版 | 08-05 |
|
||
| 沒 purge jsDelivr | 用戶按「檢查更新」像沒反應 | 08-02 |
|
||
| 沒改 `manifest.daemon` | 「檢查更新」回「manifest 沒有 daemon 版本欄位」全體失效 | 08-02 |
|
||
| 只推 bundle 沒換釘子 | 安裝器仍裝舊 bundle | 08-01 |
|
||
| 🔴 **釘子只改了常數,沒改 `[vars]`** | **部署成功但釘子沒換**——`api/latest` 與 install 頁全是舊版,白部署一次 | 08-07 |
|
||
| 用 `rsync --delete` 覆蓋 prod bundle repo | **砍掉 prod 才有的 `daemon/`(20 個安裝檔)與 `README.md`**,封測者下載不到 daemon | 08-07(推之前 `git status` 抓到) |
|
||
| 用 staging 的 manifest 整包蓋 prod | **core 從 5 顆變 24 顆**——prod 只裝 5 顆是懶載設計,會讓每個新用戶多裝 19 顆 worker | 08-07(同一個坑 `bbb433e` 有前科) |
|
||
| 用 `git worktree` 打 bundle | `0/4 bundled`——worktree 沒有 `.component-builds/` 的預編 wasm(gitignore 產物) | 08-07 |
|
||
|
||
### 🔴 換釘子:真身是 `[vars]`,不是常數(08-07 實撞,白部署一次)
|
||
|
||
```js
|
||
// installer/oauth-prototype/worker.js:74
|
||
bundleBase(env) = env.BUNDLE_BASE ? env.BUNDLE_BASE : DEFAULT_BUNDLE_BASE
|
||
```
|
||
⇒ **`wrangler.toml` 的 `[vars] BUNDLE_BASE` 優先於 `worker.js` 的常數。**
|
||
|
||
- **兩處都要改**:`wrangler.toml` 的 `[vars] BUNDLE_BASE`+`BUNDLE_BUILT`、
|
||
以及 `worker.js` 的 `DEFAULT_BUNDLE_BASE`
|
||
- **為什麼會漏**:08-07 把「指向」從常數搬進設定(為了 stage/prod 用同一套機制),
|
||
**但記載該機制的文件沒跟著改** ⇒ 照舊文件做 = 改了一半 = 等於沒改
|
||
- 🔑 **可推廣**:**改了機制,就要改記載那個機制的文件**——
|
||
否則下一個人(包括你自己)照過期文件做,會得到「做完了但沒生效」
|
||
|
||
### ✅ 覆蓋 prod bundle repo 的正確動作(08-07 定,別再整包蓋)
|
||
|
||
```bash
|
||
git reset --hard HEAD && git clean -fd # 先回到 prod 原狀
|
||
cp -R <staging>/<worker>/. <worker>/ # 逐顆覆蓋,不用 rsync --delete
|
||
# manifest:只換 prod core 清單裡「原本就有」的那幾顆 + release/built/source
|
||
git add -A
|
||
git diff --cached --diff-filter=D --name-only # ← 護欄:刪除檔案數必須是 0
|
||
```
|
||
**判準:staging 與 prod 的內容範圍不一樣(daemon 安裝檔、README、core 顆數),不能整包蓋。**
|
||
| changelog 沒寫 | 用戶不知道能不能/要不要更新 | 08-05(停在 v0.15.7,v0.18.x 全空) |
|
||
| portal 下載連結沒跟著換 | DMG 打好了但按鈕還給 zip | 08-05 |
|