Files
ISEP/skills/ship-check/SKILL.md
Leo c2638668e3 ISEP 0.1.0:環境設定收成一個 plugin,本機與雲端共用一份
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>
2026-08-20 11:41:46 +08:00

33 KiB
Raw Permalink Blame History

name, description
name description
ship-check 改完任何會影響用戶的東西之後、說「做完了」之前必讀(改雲端 workerportaldaemon workflowinstaller 都算)。也在下列時機自動載入:要打包 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 兩段都是這樣補進來的,日期都記著。)

什麼時候跑

改完任何會影響用戶的東西之後(雲端 workerportaldaemonworkflow), 在說「做完了」之前。不是收工才跑。


🚚 雲端線出貨=一個指令,不要照下面的步驟手工做(2026-08-08 起)

leo 2026-08-08:「每次做一樣的事,結果會打錯實例,就是你的出貨閘是錯的。 寫對的應該每次都機械式的做同一件事,寫錯位置也會被它修正。」 「這個出貨閘門就是廢的,它應該要像 GitHub Actions 一樣 CI/CD。」

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. 先查記憶(別重蹈覆轍)

grep -rn "版本號\|出貨\|release" system-dev/wiki/decisions-summary.md | head

必讀 D39「版本號由內容算出來,不由人宣告」

  • MAJOR.MINORRELEASE_LINE 檔(人只在大改版動)
  • PATCH 由機器決定:內容指紋一變 +1、沒變不動(重跑不虛增)
  • 真相源只有 manifest.release 一處installerlandingportal 全是讀者

「改了 code 但 release 沒 bump」= bundle 沒重打包,不是版本機制壞了。

1. 判斷這次改動影響哪條線

git -C matrix/arcrun log --oneline -5      # 雲端 workercypher/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這步最常漏

cd products/arcrun-rag
ARCRUN_REPO_ROOT=../../matrix/arcrun node installer/scripts/build-bundles.mjs --out <bundles repo>/

複驗你的改動真的進去了(不要只看腳本說成功):

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 的話

CYPHER_BASE=<實例 cypher URL> CYPHER_NS=<namespace> \
  node installer/scripts/compile-workflows.mjs

⚠️ flow 變了就不能沿用舊的預編圖,腳本會 exit 1 擋住(這個閘是對的)。 不帶 CYPHER_BASE 就跑=直接失敗,別繞過它。

3. 桌面線:打包 App

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                        # WindowsMac 上可交叉編譯)

三支機械閘必須全過(交貨前):

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 去?你檢查了嗎?

# 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-appv0.18.27 實測:strings 抓得到 LoadDirectConfigsaveDirectConfigdirect 模式符號 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.shbuild-win.sh:建置前把版本寫進 wails.jsoninfo、 建完用 trap 還原(不污染版控)。

4. 寫 changelogleo 點名要的

products/arcrun-rag/docs-site/src/content/docs/help/changelog.md

格式照既有的(寫給用戶看,不是寫 commit message):


## 📋 出貨對照表:**改了什麼 → 要動什麼**(leo 2026-08-08:「寫清楚不要再改錯」)

> 這張表是**查的**,不是用判斷的。08-08 一整天的錯全是「憑印象決定要重打什麼」。
> **每次出貨逐列對,有碰到就一定要做。**

| 你改了什麼 | 要重打/重部署 | 版本線 | 要一起改的文件 | 最後落在哪 |
|---|---|---|---|---|
| **daemon Go 程式** | 三平台打包(mac/win/msix | **daemon 版本線** | `changelog.md` **必寫** | 用戶自己的電腦 |
| **portalconsole-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 bundleuncle6)→ 裝進 youlin 的 stage 環境 → 自己測**。這一圈可以無限重複。
> **「uncle6 不碰」指的是 prod 那一側,不是 staging bundle**——別把自己的工作區也擋掉。

| 這個東西 | 住在誰的帳號 | AI 能不能動 |
|---|---|---|
| **staging bundle repo** | uncle6Gitea | ✅ **這是 AI 的工作區**,隨便改隨便推 |
| **youlin 的 stage 環境** | youlin | ✅ **AI 的測試場**,隨便裝、壞了自己修 |
| **prod bundle repo** | uncle6GitHub | 🔴 要 leo arm,且只准「**原封提升**」不准重打 |
| **prod 的安裝器/landingdocs 部署** | 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 bundleuncle6 內部: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.」**
  ⇒ **KVD1/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 repogithub-armpublish-github)。

📖 stage 救過一次的實例:見頂層 `system-dev/wiki/status.md`
「同一次測試照出一個會炸掉所有用戶的回歸(stage 第一次真的救了我們)」。

## vX.Y.ZYYYY-MM-DD

**建議更新**——一句話說「這版解決你什麼問題」。

- 🔴 **最重要那項**:用戶語言描述症狀與結果
- 其他項…

判準:用戶讀得懂「這對我有什麼差別」。 不要寫「修 t195 的 401」,要寫「修好『一個檔失敗就卡住整個資料夾』」。

5. 出貨(需 leo 開 D20 閘

# leo 親跑(AI 不得代跑)
scripts/github-arm.sh "出貨 <說明>" 30

開閘後:

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 完全不管 docsgrep docs = 0 命中)。docs 是獨立的 arcrun-docs worker 沒人手動部署它就會一直停在上一次——leo 2026-08-08 抓到:Windows 安裝說明還寫著 「解壓縮 zip、裡面有兩個 exe」和「MSIX 要開開發人員模式」,而我們早就只出單一 .exe

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(等於什麼都沒換)。

複驗要抓畫面內容,不是看部署訊息

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 會看的那兩個地方

# ① 雲端線: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.7purge 後第 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/ 的預編 wasmgitignore 產物) 08-07

🔴 換釘子:真身是 [vars],不是常數(08-07 實撞,白部署一次)

// 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_BASEBUNDLE_BUILT、 以及 worker.jsDEFAULT_BUNDLE_BASE
  • 為什麼會漏:08-07 把「指向」從常數搬進設定(為了 stage/prod 用同一套機制), 但記載該機制的文件沒跟著改 ⇒ 照舊文件做 = 改了一半 = 等於沒改
  • 🔑 可推廣改了機制,就要改記載那個機制的文件—— 否則下一個人(包括你自己)照過期文件做,會得到「做完了但沒生效」

覆蓋 prod bundle repo 的正確動作(08-07 定,別再整包蓋)

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.7v0.18.x 全空) | | portal 下載連結沒跟著換 | DMG 打好了但按鈕還給 zip | 08-05 |