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>
20 KiB
description
| description |
|---|
| Gitea issue/milestone/看板的實際操作流程——issue 是 tasks 池、milestone 是 sprint、label 是狀態、看板是 leo 的畫面 |
/issue-handle — issue 就是 tasks 池子(Gitea 版,2026-08-09 leo 定調)
🔴 **本檔 2026-08-09 整份重寫。**舊版寫的是 GitHub +
ghCLI—— 那條路 D20 擋著(機器寫入 GitHub 要 leo 開閘),而真實世界早就在 Gitea 上。gh打不到 Gitea。照舊版做會什麼都做不成。
世界觀(先讀這段,其餘都是它的細節)
- issue = tasks 池子——不管從哪來,所有要做的事都是 issue
- SDD 定案推導出來的、外面回報進來的、開發中才發現的,三種都是 issue
- 差別只在 body 裡標來源,不在「住哪裡」
- milestone = sprint(一次交貨)——AI 建得了,所以 AI 管
- project(看板)= 大目標的視覺——AI 建不了,leo 建、leo 拖
- label
s/*= 狀態——機器的真相住這裡,不住看板欄位 - CP = 選擇 tasks 的指南——決定「這次交貨先拿哪幾張」,它自己不發號
- wiki = 過程、證據、為什麼——引用
#號,不重複任務狀態
一句話:leo 拖畫面,AI 管資料。兩邊看同一批 issue,不各養一份清單。
認證(最容易安靜出錯的一步)
token 從該 repo 的 gitea remote URL 取,不另存:
TOKEN=$(git remote get-url gitea | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
- 🔴 不帶 token 打私有 repo 會回
{"message":"not found"}——長得像「這裡沒東西」- 2026-08-09 實錯:據此把 24 個 open issue 宣告成不存在,還寫了一份錯的提案
- ⇒ 「查不到」有兩種:真的沒有 vs 我沒權限看見。分不出來就不准下結論
- 分辨法:沒權限回 JSON
{"message":"not found"};路由不存在回純文字404 page not found
三條標準作業流程
- ① SDD 定案 → tasks 寫進 issues
POST /api/v1/repos/{o}/{r}/issues- 🔴
labels吃 label id(int),傳名字會 422 ⇒ 先GET /labels拿 id - body 標來源:
design 推導/修改自 #42/原生
- ② 外面進來的先驗傷,再決定要不要進 sprint
- 新 issue 預設不掛
s/,先用內建bug/question/invalid/duplicate分類 - 確認「要做、且屬於某次交貨」才掛
s/todo+ 掛 milestone - ⇒ 不是每個 issue 都是 task(查完是設定問題就 close,不進 SDD)
- 新 issue 預設不掛
- ③ 先決條件(issue 之間的關聯)
POST /issues/{n}/dependencies(我被誰擋)//issues/{n}/blocks(我擋誰)- 🔴 body 是
IssueMeta,要三個欄位:{"index":22,"owner":"Leo","repo":"arcrun-rag"}- 只傳
index會回 404IsErrRepoNotExist(同 repo 也一樣要帶 owner/repo) - 📌 這條原本是我憑 swagger 路徑列表寫進本 skill 的,沒實測,一測就錯。 端點存在 ≠ 我知道怎麼呼叫它——寫進 skill 前要真跑一次。
- 只傳
- 實測通過(2026-08-09):
#21 blocked by #22建立後,GET /issues/21/dependencies→[22]、GET /issues/22/blocks→[21] - 派工前先檢查前置是否
closed,不要靠人記得順序
狀態標籤:互斥 scope label
一條線走完,從進池子到用戶拿得到:
s/triage → s/backlog → s/todo → s/doing → s/stage →(leo 蓋章 + arm + 推 prod)→ closed
↘ s/pending(卡住/等外部/等人)
s/triage新進來的,還沒驗傷——還沒決定要不要做- 🔴 為什麼不用「留白」代表未驗傷(2026-08-09 leo 建 Triage 看板時暴露):
留白查不出來。撈得到
s/todo,卻撈不到「所有還沒驗傷的」,只能肉眼掃 ——那正是這套要拔掉的東西。沒有標籤 ≠ 一種狀態,它是查詢的死角。
- 🔴 為什麼不用「留白」代表未驗傷(2026-08-09 leo 建 Triage 看板時暴露):
留白查不出來。撈得到
s/backlog驗過了、確定要做,但還沒排進任何 sprint- = leo 說的「wishlist/功能需求/後續要規劃的」
- 與
s/triage的差別:要不要做,已經有答案了 - 與 milestone 的關係:
s/backlog= 沒掛 milestone;掛上 milestone 就該進s/todo
s/todo已排進 sprint,等開工s/doing現在有人在做s/stage已推上 stage,等 leo 去 youlin 的 stage 環境驗收(出貨流程第⑤步)- leo 2026-08-09 提:「就知道哪些要驗收」
- 🔑 它的價值是可查詢:leo 問「我今天要驗什麼」=撈
labels=s/stage一次答完, 不必總管回想、不必翻對話
s/pending卡住——等外部/等人,不是沒人做(例:#8等 leo 在真 Windows 截圖)closed用戶拿得到了——不是「程式碼寫完」,也不是「leo 看過 stage」
優先級是另一個軸:p/ scope
p/high(擋住交付或有時間壓力)/p/low(想做,但晚一點沒關係)。
- 🔴 為什麼不併進
s/:s/回答「它走到哪」,p/回答「它多重要」—— 一個s/backlog的東西也可以是p/high(想做很久、很重要、只是還沒排進這次 sprint)。 併成一組就表達不出來。 - 兩組各自 exclusive,互不干擾:一個 issue 同時有一個
s/和至多一個p/
對照 leo 的 Triage 看板欄位
Needs Triage←s/triageBacklog←s/backlogHigh Priority/Low Priority←p/high/p/lowClosed← issue 的closed狀態
⇒ 欄位仍是 leo 的視覺、標籤仍是機器的真相,兩邊講同一件事但各自可用。
🔴 為什麼用 s/stage 不用 s-stage:
s-stage 是平名標籤,會跟 s/doing 同時掛著;
s/stage 在同一個 scope 內,貼上去舊狀態自動掉 ⇒ 才是狀態機。
狀態標籤一律走 s/ 前綴,別建平名的。
- 建法:
POST /labels,body 帶{"name":"s/doing","exclusive":true} scope/name格式 +exclusive⇒ 同一 scope 下一個 issue 只能掛一個貼 s/todo → ['bug', 's/todo'] 再貼 s/doing → ['bug', 's/doing'] ← 舊狀態自動掉,不相干的 bug 留著- 🔴 不建
s/done:closed就是 done,多一個就是同一件事兩個真相 - 🔴 label 是 repo-scoped,沒有跨 repo 繼承——新 repo 要自己補
AI 做得到 / 做不到(別浪費時間試)
- ✅ 做得到:開 issue、改 issue、掛/換
s/*標籤、建 milestone、把 issue 掛進 milestone、 建先決條件、留言、結案、從GET /issues/{n}/timeline讀出「這張卡在哪個看板」 - ❌ 做不到:建 project、把卡拖進 project、知道卡在哪一欄
POST …/projects回純文字404 page not found=路由不存在- 對照組證法:同一把 token 打
POST /labels回 201 ⇒ 證明「不是我沒權限,是 Gitea 沒開這條路」。這個手法值得複用。 - issue 物件無 project 欄位;
timeline的project_board事件也沒有欄位名/id
- ❌ 不採:直寫 Gitea 的
project_issue表、web-only 路由POST /{owner}/{repo}/issues/projects/column(吃 session cookie+CSRF)- 理由不只是升級風險:那是別人家 app 的內部 schema/未公開契約, 用了等於在系統裡多一條沒人維護的隱性契約——與「兩份真相」同病
兩個會咬人的坑
labels在兩支 API 是兩種型別- 建 issue
POST /issues→ 吃 id(int),傳名字會 422:json: cannot unmarshal JSON string into Go int64 within "/labels/0" - 貼標籤
POST /issues/{n}/labels→ 吃 名字(string)
- 建 issue
- 本機是 zsh,不做參數字串切分(bash 才會)
for n in $LIST在 zsh 會把整串當成一個字 ⇒ 用${=LIST}或逐一傳參
🔴 怎麼寫一張 issue(leo 2026-08-09 連罵三次立的)
leo:「請問這個是人話嗎?誰看得懂?」 「你的 issues 要寫清楚 5W1H,你會做重複工,表示你不知道自己在幹嘛」 「先寫問題,再寫解法,不是先寫一堆細節」
🔴 為什麼要寫人話——不是為了友善,是為了讓 leo 抓得到我的錯
leo 2026-08-09:
「你搞不清楚的時候,我也無法幫你看,因為我看不懂你在寫說明。」
這是所有「白話鐵律」底下真正的理由: 看不懂 = 審查失效。 我今天在同一個 session 裡錯了好幾次 (把 24 個 open issue 讀成 0、給 leo 一台他根本沒在用的機器的網址)—— 如果我寫的東西他看得懂,這些會更早被擋下來。
⇒ 用內部代號寫票,等於把唯一能發現我出錯的人的眼睛關掉。
🔴 修好的當下就關票(今天一天撞三次)
2026-08-09 派工做 #9/#14/#17,三張都是早就修好、只是沒人關
⇒ 三個 subagent 的時間全花在確認「這件其實已經好了」。
- 不是那三張票的問題,是「修好時沒有人回來關」的問題
- ⇒ 改完 → push → 當場回票寫清楚怎麼驗的、然後 close
- ⇒ 撈任務前先問「這張票的日期多久了?」,超過一週的先驗現況再派工
起點:issue 是「報告」,不是「工單」
leo 2026-08-09:
「如果我要丟,我會寫『我遇到一個問題是…』『發現一個 bug 是…』 『我希望有一個功能是…』,怎麼會寫成這樣?」
⇒ 一張 issue 應該長得像一個人開口講話。 先用第一人稱把事情講一遍,再談要做什麼。寫成規格條目就沒人看得下去。
那三種開頭正好就是三種類型,驗傷就是在判斷它是哪一種:
- 「我遇到一個問題是…」→ 卡住了 →
question - 「我發現一個 bug 是…」→ 壞掉了 →
bug - 「我希望有一個功能是…」→ 願望/功能需求 →
enhancement(多半配s/backlog)
範例(#7 現在的樣子):
我遇到一個問題是:我要把 AI 接上我的知識庫,claude.ai 第一格就要我填 MCP 網址, 但我不知道我的是什麼。那串網址是安裝的時候長出來的,每個人都不一樣,我沒地方查。
標題:寫症狀,不寫修法
反例(真的誤導了好幾個月的那一句):
t151 收尾:安裝器要生成並下發 arcrun-mcp 的 MCP_OWNER_SECRET(擋封測,缺它每個封測者都死在同意頁)
└代號┘ └────────── 這整段是「怎麼修」,而且是後來被推翻的舊解法 ──────────┘ └─ 唯一人話在括號裡 ─┘
改成:「用戶不知道自己的 MCP 網址是什麼,所以接不上 AI」
- ① 標題寫症狀不寫修法——修法會變,症狀不會。 上面那句最毒的地方不是難懂,是它把「當時以為的修法」寫死在標題上; 後來 owner secret 這條路被 portal 帳密取代,標題還停在舊解法,於是持續指錯方向。
- ② 標題裡不准出現只有我們懂的字:代號(
t151)、零件名(MCP_OWNER_SECRET)、 檔名(build-bundles.mjs)一律降到細節區。 - ③ 一句話講完「誰+卡在哪」,看標題就知道要不要點進去。
內文:問題 → 解法 → 細節(細節收進 <details>)
## 問題
<用戶視角一句話:他想做什麼、卡在哪、結果怎樣>
## 解法
<一句話:要做出什麼>
## 怎麼驗
<走用戶真的會走的那條路;貼實際畫面,HTTP 200 不算>
---
<details><summary>細節</summary>
現況實查(指令+輸出)/真身在哪個 repo 哪個檔/可照抄的既有寫法/
leo 原話/不屬於本 issue 的(已拆到 #N)
</details>
為什麼細節要收起來:leo 掃 issue 是在決定「這件要不要現在做」, 細節是做的人才需要的。攤開來就掃不動 ⇒ 他掃不動 ⇒ 這件事不會發生。
🔴 討論票 → 執行票;追蹤大的(2026-08-10 定案,D58)
leo:「這個 issue 是對於某個建議的討論過程,中間會產生一個新的 issue 就是依照討論執行某個修正,追蹤大的。」 leo:「充分利用 gitea 的機制,不要硬做個不支援的機制,容易出錯。」
- 討論票=一個建議的討論過程(脈絡、來回、為什麼)。不當工單追。
- 執行票=從討論裡長出來的「去做某個修正」。有自己的擋點、驗收、
s/*狀態。 - 追蹤執行票;孫任務留在執行票裡當 checkbox(見下一段,那條沒變)。
定址:用 Gitea 原生的 issue 引用,不要自製結構
Gitea 原生就會把 issue 引用自動變成可點連結 ⇒ CP/SDD 不必手貼網址、不必造錨點。
- 🔴 最好用的判準:需要指到票裡的某一行 = 那張票該拆了。 這個限制反而強迫出正確的顆粒度。
- 刀口不是「大小」,是「狀態」:問「它會不會需要跟隔壁那條不同的狀態?」
- 會 → 獨立成執行票
- 不會 → 留在票裡當 checkbox
兩個極端都撞過,別再撞
- 太大:
Leo/mira#2一張票 29 個 checkbox、五個段落五種狀態,卻只掛得了一個s/pending- ⇒
⑤-1~⑤-3整段作廢了好幾小時沒人發現——29 條的票不會有人逐條稽核
- ⇒
- 太小:08-09 把 stage 一件事拆成五張(
#27/#29~#32),leo 打開就看不下去
⚠️ 已作廢:隱形錨點 <a id="tNNN">(D53,同日上線同日推翻)
不要撿回去。 它是塞 raw HTML 偽造 checkbox 錨點,靠渲染器願意保留、 靠沒人用網頁編輯器把它洗掉——脆弱,而且壞掉時看不出來。
📌 它錯在哪值得記:整段推理是嚴謹的(實測三則、對照兩個反例、接上既有鐵律), 但沒有先問「這個平台原生支援嗎」。與 08-09「平台已經做了的事我又做一次」同族, 只是這次是平台沒做的事我硬做。 ⇒ 造任何機制之前先問兩句:「原生支援嗎?」「不支援是不是在告訴我方向錯了?」
🔴 子任務用 issue 內的工作項目清單,不要開成一堆票(2026-08-09 實撞)
leo:「子任務在這裏寫就好了」(指 Gitea 編輯器的工作項目清單按鈕)「現在這樣太亂了」
- 一件事 = 一張票,票裡用
- [ ]列子任務(每條帶錨點,見上一段) - ❌ 不要把每個子任務開成獨立的票
- 2026-08-09 實錯:總管把 stage 這一件事拆成
#27/#29/#30/#31/#32五張, leo 打開就看不下去。已收攏回#27一張,其餘關閉 - 病根:把 leo 說的「寫在 issues 裡的新增工作項目清單」讀成「開新 issue」, 而他指的是編輯器裡那顆 checkbox 按鈕
- 2026-08-09 實錯:總管把 stage 這一件事拆成
- 🔑 規則(leo 原話):
「整個 tasks 內容用 markdown 寫進去,除非某個任務開始工作後變大了,就要獨立成一張票。」
- ⇒ 預設全部寫成 checkbox,包含整份 SDD 的 tasks
- ⇒ 獨立成票是「事後」的動作,不是規劃時就先拆
- 觸發時機=開始做了之後才發現它變大(要多輪、要另一個 repo、要另一個人)
- 那時再把那一條抽出來開票,並在原票的該行改成
- [ ] 見 #NN
- ⇒ 不要在規劃階段就預先拆票——那正是 2026-08-09 弄亂的做法
- 另開一張的另一個正當理由:主詞不同(見下一段) ——例如「網址看得到」vs「進得去」是兩個人在抱怨兩件不同的事,那本來就不是同一件
🔴 一筆 issue 只做一件事(2026-08-09 實撞)
leo:「#18 其實是 2 個分開的任務⋯⋯一半已經完成卡在另一半,但另一半根本不相依」
- 綁在一起的代價:做完的那半被沒做的那半拖著,看起來像沒完成 ⇒ 進度儀表失真,而 leo 是靠這個判斷還剩什麼
- 判準(同
cp-write鐵律 5):主詞不同就拆- 「把設定搬進應用視窗」=設定放哪的問題
- 「新增 onboarding」=第一次的人怎麼上手的問題
- 兩者互不依賴 ⇒ 一定是兩張
- 🔴 不准用「併入此案」把別的 issue 吞進來
- #18 原本寫「併入此案:狀態顯示(見另一 issue)」,而那是獨立存在的 #17
- ⇒ 同一件事在兩張票上,又是兩份真相;要關聯用
dependencies/blocks,不用文字吞併
- 拆單時要留痕:兩邊都寫明「從 #N 拆出」與「為什麼不相依」, 否則下一個人會以為是重複開單
🔴 設計寫在票上——不要每次回去 grep 源碼(leo 2026-08-09 立)
leo:「你應該把 design 寫在 issues 裡,你就可以去查 issues,而不是每次去查源碼。」
為了回答問題而查出來的「它現在怎麼運作」,當場寫回那張票,附 檔案:行 當出處。
- 當天實錄:同一個 session 裡,為了回答四個問題各翻了一次源碼——
console 現在用來做什麼(
installer/oauth-prototype/worker.js:3237)/ 迎新引導為什麼看不到(main.js:73只在零帳號時出現)/ 重裝會不會重設密碼(/console/setup已存在回 409)/ bundle 是快照還是連結(manifest 的source欄與js_bytes)。 四題的答案都值得留在票上,卻只留在對話裡 ⇒ 對話結束就沒了,下次再翻一次。 - 🔴 而且翻源碼拿到的是「稿子」:它反映「還沒清乾淨」,不等於「還在用」。 同一天就因為讀源碼推論而把兩台實例的定位講反(geek=prod/youlin=stage)。
- 票上該有的三件:它現在怎麼運作/為什麼這樣設計/動它會影響誰——不只是「要做什麼」。
- 順序:先查票 → 查不到才翻源碼 → 翻完回頭補進票(與 wiki 同一階梯)。
- 判準一句話:如果我剛剛翻了源碼才知道答案,那就是一條該補進票的設計記錄。
界線(沿用,未變)
- 跨 repo 的 issue/comment 一律開頭署名
[<本 repo> CC]/[InkStoneCo 總管]- 所有 repo 共用同一個帳號,author 看不出是誰發的,身份只能在內容層自報
- 發 issue 給別的 repo 要先問人;對自己 repo 開 issue 記待辦則直接做
- 🔴 有事才讀,禁止自動輪詢——禁 Actions/cron/webhook 因 issue 事件 fan-out (那正是當初害 GitHub 帳號被 flag 的流量模式;換成 Gitea 也不放寬)
🔴 標 s/stage 不等於交棒完成——要附「怎麼測」
leo 2026-08-09:「要我來測試是嗎?如果這樣,你要在總管回覆中說明測試方式, 而不是『要測試』,我就會按照它來測試。」
- 把票改成
s/stage/s/pending只是改了一個欄位,leo 看到的是「該你了」卻不知道怎麼做 ⇒ 等於把找路的成本丟給他,正是 principles「不增加 leo 負擔」要防的 - 每一件推給 leo 的事,回覆裡必須帶三樣:
- 他要打開什麼(確切網址/確切指令,不是「去 portal 設定頁找」)
- 他該看到什麼(正確的樣子長怎樣)
- 什麼情況算失敗(看到什麼就是壞的,回一個詞給我)
- 🔴 指令必須自己先打過。給沒驗過的網址/指令 = 把除錯成本丟給他
- 2026-08-09 實例:我差點把
arcrun-mcp.yuga3bse.workers.dev給他—— 實打是 HTTP 000(DNS 不解析)。正確的是WORKER_SUBDOMAIN(deployment-map.md:176=youlin-hsieh-dev),實打回 401unauthorized=端點在、要 OAuth,這才是對的行為 - ⇒ 401 和 000 都不是 200,但意義天差地遠:前者證明東西在,後者證明我在瞎猜
- 2026-08-09 實例:我差點把
收工判準
回覆 issue 要寫做了什麼、為什麼這樣決定、動了哪些檔,不是只回「done」。
結案要有實測證據——「程式碼寫完了」不是狀態(見 CRITICAL-PATH.md)。
⏳ 尚未驗證的一條(別當成已知)
把卡拖到 Done 欄,Gitea 會不會自動 close 那個 issue? 2026-08-09 想測,但那張測試卡被刪掉了 ⇒ 無證據,不編。 若答案是「會」,拖拉就天然被機器看見,這條縫自動消失。