--- description: Gitea issue/milestone/看板的實際操作流程——issue 是 tasks 池、milestone 是 sprint、label 是狀態、看板是 leo 的畫面 --- # /issue-handle — issue 就是 tasks 池子(Gitea 版,2026-08-09 leo 定調) > 🔴 **本檔 2026-08-09 整份重寫。**舊版寫的是 GitHub + `gh` CLI—— > 那條路 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 取,不另存: ```bash 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 之間的關聯)** - `POST /issues/{n}/dependencies`(我被誰擋)/`/issues/{n}/blocks`(我擋誰) - 🔴 **body 是 `IssueMeta`,要三個欄位:`{"index":22,"owner":"Leo","repo":"arcrun-rag"}`** - 只傳 `index` 會回 **404 `IsErrRepoNotExist`**(同 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`,卻撈不到「所有還沒驗傷的」,只能肉眼掃 ——那正是這套要拔掉的東西。**沒有標籤 ≠ 一種狀態,它是查詢的死角。** - `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/triage` - `Backlog` ← `s/backlog` - `High Priority`/`Low Priority` ← `p/high`/`p/low` - `Closed` ← 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)** - **本機是 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`)一律降到細節區。 - **③ 一句話講完「誰+卡在哪」**,看標題就知道要不要點進去。 ### 內文:問題 → 解法 → 細節(細節收進 `
`) ```markdown ## 問題 <用戶視角一句話:他想做什麼、卡在哪、結果怎樣> ## 解法 <一句話:要做出什麼> ## 怎麼驗 <走用戶真的會走的那條路;貼實際畫面,HTTP 200 不算> ---
細節 現況實查(指令+輸出)/真身在哪個 repo 哪個檔/可照抄的既有寫法/ leo 原話/不屬於本 issue 的(已拆到 #N)
``` **為什麼細節要收起來**: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 打開就看不下去** ### ⚠️ 已作廢:隱形錨點 ``(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 按鈕** - 🔑 **規則(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`),實打回 401 `unauthorized` =端點在、要 OAuth,**這才是對的行為** - ⇒ **401 和 000 都不是 200,但意義天差地遠**:前者證明東西在,後者證明我在瞎猜 ## 收工判準 回覆 issue 要寫**做了什麼、為什麼這樣決定、動了哪些檔**,不是只回「done」。 結案要有**實測證據**——「程式碼寫完了」不是狀態(見 `CRITICAL-PATH.md`)。 ## ⏳ 尚未驗證的一條(別當成已知) **把卡拖到 Done 欄,Gitea 會不會自動 close 那個 issue?** 2026-08-09 想測,但那張測試卡被刪掉了 ⇒ **無證據,不編**。 若答案是「會」,拖拉就天然被機器看見,這條縫自動消失。