Communitygithub.com

lenneTech/maintaining-lt-stack

維護與發佈 lt 基礎儲存庫(「Grund-Repos」)的堆疊:依賴順序(從 nuxt-extensions 到 nuxt-base-starter,從 nest-server 到 nest-server-starter)、每個儲存庫的發佈流程,以及作為發佈閘道的冒煙測試。由「maintain stack」、「release all repos」、「stack release」、「Grund-Repos aktualisieren」以及後端 /lt-dev:publish 觸發。不適用於單一 npm 套件(請使用 maintaining-npm-packages)。不適用於客戶專案內的 nest-server 升級(請使用 nest-server-updating)。

maintaining-lt-stack 是什麼?

此技能會自動化 **lenneTech** 技術堆疊中七個基礎儲存庫(「Grund-Repos」)的維護與發佈流程。它會根據依賴關係圖表協調發佈,以確保正確的順序(例如,先發佈 nuxt-extensions 與 nest-server,再發佈其各自的 starter 範本),並以全面的冒煙測試進行驗證。在多個儲存庫之間處理平行發佈時,此技能使用點對點(peer)任務協定,透過共享的「分類帳(ledger)」來管理並發會話、通知(READY)、解決環境問題(SOLVED)以及協調最終的冒煙測試(LANDED)。它強制執行關鍵規則,例如協調跨框架套件(如 better-auth)的版本、確保在版本號遞增之前進行程式碼審查,以及僅在實際有變更時才發佈。此技能專為 Claude Code 設計,並與 Cursor 和 Codex 相容。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lenneTech/claude-code/tree/HEAD/plugins/lt-dev/skills/maintaining-lt-stack

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

維護 lt 堆疊(所有基礎儲存庫)

七個基礎儲存庫會依照依賴順序進行維護與發佈。權威來源為 github.com/lenneTech;釋出需要每個儲存庫的本地克隆,且其檢出路徑因機器而異 — 請透過以下方式定位而非假設路徑:

find "$HOME" -maxdepth 5 -type d -name nest-server-starter -not -path '*/node_modules/*' 2>/dev/null

若某個儲存庫尚未在任何位置檢出,請將其克隆到與其兄弟儲存庫相同的工作區目錄中。目標最終狀態:所有儲存庫皆為最新版本,check 在所有地方皆為綠色,npm 套件已發佈,範本已標記 — 並透過完整的 /lt-dev:fullstack:smoke-test 執行來驗證。

依賴關係圖(決定順序)

第 1 波(可平行化):   nuxt-extensions   nest-server   lt-monorepo   cli
                             │                │
                             ▼ (等待 npm 發佈!)
第 2 波(可平行化):   nuxt-base-starter  nest-server-starter
                             │
                             ▼
驗證:                /lt-dev:fullstack:smoke-test  (實際演練所有儲存庫)
                             │
                             ▼
第 3 波(僅在有發現時):修補程式修正 → 重新發佈受影響的儲存庫(nest-server-starter:僅推送)

規則: 僅當其 npm 套件實際上可在 npm 上解析時,才會更新 starter,而非在 GitHub 發佈存在時 — publish.yml 動作需要幾分鐘時間。

請詢問特定版本的端點,而非 npm view。 npm view 和 https://registry.npmjs.org/<pkg> 讀取的是相同的 CDN 快取套件清單,且落後發佈數分鐘:

curl -s -o /dev/null -w '%{http_code}' https://registry.npmjs.org/<pkg>/<version>
# 200 = 現在可解析。替代方案:使用 -H 'Cache-Control: no-cache' 加上查詢參數來清除快取。

2026-09-04 測量:一個會話輪詢了 npm view 十分鐘,並讀取到「尚未發佈」,而 @lenne.tech/[email protected] 實際上已上線 — 特定版本端點立即回應了 200。從呼叫端來看,「尚未存在」和「儀器正在讀取快取」看起來一模一樣,這就是為什麼端點是應該詢問的對象。

跨平行會話執行波浪

上述波浪提到「可平行化」,這意味著:每個儲存庫一個 Claude Code 會話,在自己的終端機中,這就是堆疊發佈實際加速的方式。第 1 波包含四個獨立儲存庫,第 2 波包含另外兩個,因此實際耗時的下限是一個儲存庫的發佈時間,而非依序處理六個。

這只有在會話協調四件事時才起作用。所有這些都運行在 coordinating-peer-sessions 協定上,且按儲存庫拆分正是該協定要求的邊界:沒有兩個會話會接觸同一個工作樹。

1. 在開始之前先聲明一個儲存庫。 首先在分類帳中記錄,然後宣布它:

bash "${CLAUDE_PLUGIN_ROOT}/scripts/peer-ledger.sh" read                          # 誰已經擁有了什麼
bash "${CLAUDE_PLUGIN_ROOT}/scripts/peer-ledger.sh" claim "repo:nest-server" "第 1 波發佈"

分類帳部分在此處比任何其他地方都更重要:一輪發佈運行了數小時,會話來來去去,且僅存在於訊息中的聲明在其終端機關閉時就會遺失。claim 拒絕另一個活躍會話持有的儲存庫並命名它;其會話已死亡的聲明會讀取為 [stale] 且可自由佔用,因此崩潰永遠不會導致儲存庫擱淺。當儲存庫釋出時,使用 peer-ledger.sh release "repo:nest-server" "<version>" 釋放。沒有這些,兩個會話會發佈 nest-server,而第二個會在第一個之上創建一個版本。

[CLAIM] nest-server — 承接此儲存庫的第 1 波發佈。
涉及:堆疊發佈;其他會話不應對其進行版本控制或發佈。
需要:選擇一個不同的第 1 波儲存庫。
可用:當我報告 READY 並帶有已發佈版本時。

2. 使用 READY 發出第 2 波訊號,不要讓它輪詢。 波浪之間的等待是真實的 npm 傳播延遲,且等待的會話無法知道何時清除,除非反覆詢問註冊表。發佈會話確切知道何時。它會發送一個包含版本的 READY:

[READY] @lenne.tech/[email protected] 已發佈且可在註冊表上解析。
涉及:nuxt-base-starter — 第 2 波依賴項更新可以開始。
需要:pnpm add @lenne.tech/[email protected] 並繼續。

第 2 波會話也可以訂閱第 1 波會話上的 notify_when_idle 而不是輪詢。無論哪種方式,沒有人會在 sleep 迴圈中對抗註冊表。

3. 針對任何環境問題發送 SOLVED。 此技能中的經驗性陷阱幾乎都是機器範圍內的,而非特定於儲存庫:一個靜默返回空值的遠端讀取、一個尚未傳播的註冊表、一個備份的 CI 執行器佇列、一個損壞的工具鏈版本。第一個診斷出某個問題的會話已經付出了代價,而其他五個會話正走向同一堵牆。一個 SOLVED 就能讓他們避免。

[SOLVED] `git ls-remote` 在 stderr 上失敗且 stdout 為空,因此標籤檢查讀取為「無標籤」。
涉及:此發佈輪次中的每個儲存庫;您的標籤驗證以相同方式說謊。
需要:檢查退出碼,或透過 HTTPS 讀取 — 請參閱推送通道規則。

4. 使用 LANDED 報告完成,以便冒煙測試開始一次。 驗證閘道會在所有儲存庫釋出後運行。最後完成的人啟動它;其他人則表示已完成並停止。

什麼不通過訊息傳遞:存在哪些儲存庫以及它們的順序為何(此技能已說明),以及儲存庫處於哪個版本(註冊表和標籤已說明)。而且沒有會話會為另一個會話分配一個儲存庫。使用者決定誰拿什麼,或者每個會話聲明一個自由的並說明是哪個。

跨領域規則(所有儲存庫)

  • 關鍵的連線套件應同時移動到兩個框架儲存庫中。 某些依賴項不是一個儲存庫的事務 — 它們是一個具有兩個端點的協定,而 nest-server 和 nuxt-extensions 各自擁有一個端點。better-auth 是教會我們這個案例的套件;將兩個框架都以相同方式導入的任何套件視為相同處理。

    兩者都將其聲明為具有一個對等依賴項,其範圍在兩個清單中是完全相同的位元組,且範圍狹窄:>=<已知最低良好版本> <<下一個次要版本>,永遠不要使用 ^。better-auth 在 MINOR 版本中會中斷,因此插入符號會在一次發佈後邀請相同的分裂。從清單中讀取當前範圍,而不是從此處 — 它會移動,且寫入此技能的版本是一個會過時的版本:

    node -e 'for (const p of ["nest-server","nuxt-extensions"]) console.log(p, require(`${process.env.HOME}/code/lenneTech/${p}/package.json`).peerDependencies["better-auth"])'
    

    更新它意味著:在 nest-server 和 nuxt-extensions 中提高範圍,在 nest-server-starter 和 nuxt-base-starter 中釘選新版本,然後發佈所有四個。永遠不要只做一半,即使只是「為了解除前端阻塞」。僅提高下限也是一次更新 — 它說明了哪個補丁是已知良好的,而兩個儲存庫之間不一致的下限與版本分裂是相同的缺陷,只是早了一個發佈。

    下限不是品味問題:它也必須滿足 SIBLING 對等依賴項。 一個像 better-auth 這樣的套件附帶有同伴(@better-auth/passkey、@better-auth/core),這些同伴反過來需要一個特定版本的它。低於同伴要求的下限會批准一個實際上無法安裝的配對 — >=1.7.0 允許 better-auth 1.7.0 與 passkey 1.7.1 並存,而 passkey 本身拒絕這個組合(^1.7.1)。這個範圍並非一般性的錯誤;它恰好允許一個無效的交叉組合,正是那種在任何一個儲存庫中都永遠不會遇到的安裝。從同伴的清單中推導出下限,而不是憑直覺:

    node -e 'console.log(require("./node_modules/@better-auth/passkey/package.json").peerDependencies)'
    

    nuxt-extensions 在 test/peer-dependency-ranges.test.ts 中機械地斷言這一點(下限與同伴的要求,以及每個範圍與實際測試的 devDependency)。值得在對等範圍有同伴的任何地方複製此模式。

    分裂的成本,測量:nest-server 將 better-auth 1.6.26 釘選為硬依賴項,而 nuxt-extensions 聲明為對等依賴項,因此應用程式升級到 1.7.1 而 API 無法跟進。1.7 為 twoFactor.enable 提供了帶有 method 的區分結果,而 1.6.26 從未發送 — 每個全端專案中的每個 2FA 啟用都失敗了,並帶有通用客戶端錯誤且伺服器日誌中沒有任何異常。兩個儲存庫的 check 在整個時間都是綠色的:每個內部都是一致的,只有組合的工作區才同時擁有兩半。

    今天有三個守衛可以捕捉到它,但它們都無法取代此規則 — 它們捕捉錯誤,而規則預防錯誤:

    • 當清單被合併時(lt CLI hoist-workspace-pnpm-config.ts,自 1.45.0 起)。提升是最後寫入者獲勝,這對於根目錄與子目錄是正確的,但對於兄弟專案之間則是一個陷阱。報告:兩個子專案在同一運行中設定同一個金鑰不同;增量案例(add-api 然後 add-app,其中較早的運行已經提升並清空了其來源);以及一個儲存庫在 package.json#pnpm 及其自己的 pnpm-workspace.yaml 之間與自身矛盾。allowBuilds 不僅被報告,而且被合併為拒絕優先 — 一個警告並不會停止某個專案明確拒絕的安裝腳本,而 overrides 僅保留報告狀態,因為對於一個版本沒有安全的方向。

    • 在組合的工作區中(lt-monorepo/scripts/check-workspace-consistency.mjs,自 3.10.0 起)。當 api 和 app 以不同方式解析關鍵連線套件、當兩個框架承諾不同的對等範圍,或當某個成員已安裝該套件但未將其釘選在任何地方時 — 失敗 — autoInstallPeers 預設為 true,因此一個未釘選的對等依賴項今天同意,並在下次安裝時自由漂移。

    • 在行為上(nuxt-base-starter/.github/workflows/test.yml,job e2e-auth,自 2.22.3 起為綠色)。啟動 MongoDB 和 nest-server-starter,並針對真實 API 運行認證套件。這是唯一能看到破裂合約(而非版本差異)的層級 — 另一個層級比較聲明,而在整個分裂期間聲明都是綠色的。

      有兩件事它需要且容易遺漏,這兩件事都是由其首次運行發現的:範本的依賴項必須單獨安裝(它有自己的鎖定檔案,且其檢查鏈以 cross-env 開始 — 它自己的 devDependency 之一),而一個空的資料庫會將每個訪客路由到 /auth/setup,因此在套件執行之前,必須透過 POST /system-setup/init 創建第一位管理員。

    冒煙測試是端到端的閘道:它構建一個真實的全端專案,因此它是連線分裂表現為失敗流程(而非版本差異)的地方。

  • 在 /lt-dev:review 運行過該儲存庫的發佈差異之後才能發佈(/lt-dev:review --base=<最後一個發佈標籤>,其中包括未提交和未追蹤的檔案),在其維護之後和其版本遞增之前。從 origin/main 獲取標籤:git describe --tags --abbrev=0 origin/main。發佈標籤位於每個基礎儲存庫的 main 上,而 nest-server 的標籤是 develop 從不包含的合併提交,因此從 HEAD 查找會返回舊了數個發佈的標籤(2026-10-27 在 nest-server develop 上測量:返回 11.33.1 而非 12.0.0,審查了 851 個檔案而非 14 個)。鏈中的每個儲存庫都會根據自己的差異通過審查;審查無法修復的發現會阻止該儲存庫的發佈及其下游所有內容。由 Kai 於 2026-10-21 決定,以便自動化發佈鏈永遠不會發佈未經審查的程式碼。

  • 沒有變更 → 沒有發佈。 唯一跳過發佈的情況是一個儲存庫中確實沒有任何變更(工作樹是乾淨的,且自上次發佈版本以來沒有提交)— 永遠不要創建一個完全沒有變更的版本。任何實際的儲存庫變更 — 依賴項更新、程式碼、腳本、鎖定檔案、工具鏈釘選 — 都證明了一個新版本是合理的;不要猜測某個變更是否「值得發佈」。一個未變更的儲存庫被報告為「已是最新 — 無需發佈」並跳過。一個例外是 nest-server-starter:其版本僅隨著 nest-server 版本移動,因此其自身的變更會在沒有發佈的情況下被提交和推送,並在下一個版本中發布(recipes.md,nest-server-starter)。 參考是已發佈的工件:對於 npm 套件,無法到達 tarball 的提交(在 package.json files 集合之外 — 例如 .claude/agent-memory/**、CI 配置)不會觸發自身的發佈;在不確定時使用 npm pack --dry-run 驗證。此類提交僅隨下一次實際發佈一起攜帶。對於範本,工件是儲存庫本身,因此每個提交都算數。

  • 推送通道: 詢問腳本,它功能性地決定:

    bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-push-channel.sh" [<repo-root>]   # 在儲存庫內部省略;永遠不要傳遞 "$(pwd)"
    # -> ssh<TAB>github.com<TAB>authenticated as: Hi kaihaase! …
    # -> https<TAB>github.com<TAB>no SSH authentication (…)
    

    若是 https,請透過 HTTPS 推送: git -c credential.helper='!gh auth git-credential' push https://github.com/lenneTech/<repo>.git <branch>。 gh release create 無論如何都不受影響。

    當推送因讀起來像權限問題而失敗時,請執行檢查。 2026-09-08 在 nest-server-starter 上測量:git push origin main 失敗,顯示 Please make sure you have the correct access rights and the repository exists,而在同一分鐘內透過 SSH 推送到 nest-server 卻成功了。帳戶、合作者狀態或遠端 URL 沒有任何問題 — 僅對該路徑的 SSH 到 github.com 是死的,檢查顯示了 https<TAB>github.com<TAB>no response from …。

    取決於語境,替代推送公式:

    • 對於 gh 目前不支援的遠端(例如,非 GitHub 主機),請檢查 credential.helper 是否設定為 osxkeychain 或 wincred — 在這種情況下,請使用 git push origin main,因為這些 helper 會提示互動式憑證。
    • 對於巨大的回購推送(由 git count-objects -vH 診斷),請包括 -c core.bigFileThreshold=1g、-c http.postBuffer=2g 並考慮 git push --mirror(不影響 --tags,因為 --mirror 會鏡像引用規範)。
  • tarball 內容等同於懷的最大努力來源。 一個 npm publish 發送的內容與 npm pack 在 CI 上顯示的內容相同 — 由 package.json files 欄位過濾,加上 ! 否定模式 — 而不是儲存庫中存在的內容。階梯式發佈鏈只能在工件的基礎上演練,而不能在儲存庫的基礎上演練,這就是為什麼冒煙測試(而不是 npm view)是所有儲存庫最終發佈的驗證閘道。規則:gin(fundament/gin)聲明一個本地路徑 ../../.mise/bin:../../bin 在其 PATH 操作中,該路徑在儲存庫中解析良好但在 tarball 中缺失 — npm pack --dry-run 直接揭示了它。files 欄位編譯成 tarball 的內容是唯一在發佈後存在的內容。

  • 將發佈約定為單一、順序的活動。 一個正在進行的發佈輪次中的儲存庫不得有平行發佈 — 永遠不要發布一個版本,然後在同一個儲存庫上發布第二個版本。這會混淆下游(是 [email protected] 還是 1.3.0 與 nuxt-base-starter 一起?)並使 npm view 結果在幾分鐘內不值得信任。冒煙測試作為單一序列化點:它在所有儲存庫都輸出後開始,其結果必須是每個儲存庫的最終輸出。一個發佈輪次中的所有版本都簽署到一個合規追蹤中。

    例外:僅文本的流程(README 拼寫修復、LICENSE 更新、.gitignore 行)在空間中未對齊時,可以獨立於另一個發佈輪次推送。冒煙測試不需要重新運行。

  • 標籤必須推送: git push origin main --tags。gh release create 創建一個帶有輕量級標籤的版本;如果一個人在推送包含該提交的分支之前推送標籤,視覺上它存在,但 git ls-remote --tags origin 直到分支推送後才會列出它。--follow-tags 僅推送由目前分支上的提交所指向的註解標籤 — 而發佈標籤是註解的還是輕量級的取決於 CLI 和版本號的長度。兩階段推送(版本推送 main,然後 --tags,總是兩者)是最簡單的規則,永不誤判。

    有兩種方式在忽略標籤的情況下推送:一個恐懼新標籤的 pull --rebase,和一個檢查合併衝突並在 push 之前放棄標籤的 git merge origin/main。這兩個都只影響 Git 狀態,而不影響 gh release — 註解標籤附加到一個當前存在的物件上,所以它們由一個常規的 git push --tags 發送。如果連續推送,第二個分支可能會推送帶有與第一個相同的雜湊值的標籤名稱,在這種情況下,gh 為了請求者創建了一個重複的版本(同一個作者,同一個帳戶,同一天,對同一個提交 — 沒有第二個標籤)。第三個則在失敗時運行 git tag -d <tagname> 和 gh release delete <tagname>。gh 有權限刪除歌利亞未創建的發佈,因為授權儲存庫用於 gh 範圍。

  • 不要清理分支。 任何 git branch -d 或 git remote prune origin 都屬於日常開發而非發佈。GitHub 用於分叉的 reflog 比本地的更可靠,且在 nest-server 的跨模型背景下,使用者看到的一個分叉可能包含儲存庫中不存在的工作。發佈輪次中的清理是有風險的:分支可能包含未合併的提交,這些提交在沒有分支引用時難以恢復。

  • 內部儲存庫地址。 基礎儲存庫的生命週期意味著它們的遠端 URL 是固定的:origin 是 github.com/lenneTech/<repo>。如果一個使用者克隆他們自己的分叉,origin 指向分叉,則 upstream 指向基礎儲存庫。腳本期望從 upstream 和 origin/main(通常是 upstream/main)獲取基礎儲存庫的提交 — git merge upstream/main 是正確的用於發佈的動作。在互動式會話中,git remote -v 由檢查腳本完成;git branch -a | grep -oE '(origin|upstream)/main' 由發佈腳本用於發佈。

  • 巢狀的承諾進入工作樹。 一個命令如 git stash 創建了一個未參考的 blob,該 blob 不被清理,且在修改後的工作樹中可能與未儲藏的文件發生碰撞。規則:git reset --hard origin/main 對於一個同步的儲存庫是安全的,但 git stash 不是。發佈腳本在其序言中執行 git status --porcelain 以確保工作樹是乾淨的,然後再繼續。

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

相關技能