Files
ISEP/commands/issue-handle.md
T
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

347 lines
20 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: Gitea issuemilestone/看板的實際操作流程——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 idint**,傳名字會 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 cookieCSRF
- 理由不只是升級風險:**那是別人家 app 的內部 schema/未公開契約**,
用了等於在系統裡多一條沒人維護的隱性契約——與「兩份真相」同病
## 兩個會咬人的坑
- **`labels` 在兩支 API 是兩種型別**
- 建 issue `POST /issues` → 吃 **idint**,傳名字會 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}` 或逐一傳參
## 🔴 怎麼寫一張 issueleo 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>`
```markdown
## 問題
<用戶視角一句話:他想做什麼、卡在哪、結果怎樣>
## 解法
<一句話:要做出什麼>
## 怎麼驗
<走用戶真的會走的那條路;貼實際畫面,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 按鈕**
- 🔑 **規則(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=prodyoulinstage)。
- **票上該有的三件**:**它現在怎麼運作/為什麼這樣設計/動它會影響誰**——不只是「要做什麼」。
- **順序**:先查票 → 查不到才翻源碼 → **翻完回頭補進票**(與 wiki 同一階梯)。
- **判準一句話**:**如果我剛剛翻了源碼才知道答案,那就是一條該補進票的設計記錄。**
## 界線(沿用,未變)
- **跨 repo 的 issuecomment 一律開頭署名** `[<本 repo> CC]``[InkStoneCo 總管]`
- 所有 repo 共用同一個帳號,author 看不出是誰發的,身份只能在內容層自報
- **發 issue 給別的 repo 要先問人**;對自己 repo 開 issue 記待辦則直接做
- 🔴 **有事才讀,禁止自動輪詢**——禁 Actionscronwebhook 因 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 000DNS 不解析)**。正確的是 `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 想測,但那張測試卡被刪掉了 ⇒ **無證據,不編**
若答案是「會」,拖拉就天然被機器看見,這條縫自動消失。