今天要解的問題

我的 kiwi-skills(sean2249/skills)原本自己寫了一整套 issue 流程:issue-enhance 寫計畫、issue-implement 實作加審查加 merge、issue-orchestrate 讓主 session 開子 session 平行跑(怎麼長出來的,寫在〈把 issue 工作流制式化成共用 skill〉)。

但這套裡實際跑起來有不少笨重的東西,也有時候過於僵化。這時體驗過一把自己的工作流後,就想說回到 Matt Pocock 的 mattpocock-skills ,他內部本來就有 /triage、/to-spec、/to-tickets、/implement、/implement-spec,做的正是 spec 到 implement 這一段。所以就想將底層全部換成他的,不要去修改他的 skill,若是不符合的,再伸出新的 skill。

只是一開始我不確定影響範圍多廣。最後動到的是 skills 本身,加上 7 個要套用這套流程的 repo,共 8 個。

技術背景

Matt 的 skill 用五個角色講 issue 的狀態,每個 repo 用一份 triage-labels.md 把角色對到自己的 label(例如 skills 自己的 docs/agents/triage-labels.md)。我原本就有 status:* 這套 label,所以直接對上去:

Matt 的角色我的 label意思
needs-triagestatus: raw待評估
needs-infostatus: needs-info等補資訊
ready-for-agentstatus: approved可以交給 agent
ready-for-humanstatus: ready-for-human要人來做
wontfixstatus: wontfix不做
bug(分類)type: bug壞掉了
enhancement(分類)type: enhancement新功能或改進

因為只是對照表,既有的 66 張 issue 一張都不用搬。

想法與取捨

  • 減少在 github issue/pr 的資訊暴露。還是會擔心不小心將金鑰或被 prompt injection 攻擊而洩漏出去
  • 減少個人 skill 的內容。認清自己寫 skill,還沒有到很好,站在巨人的肩膀
  • 捨棄跨 session 的拆分,只有 to-spec 與 implement。也不能像上一版可以隨時從中斷點連接,主因是感覺很像也沒那個必要性,context window 沒有到那麼貧乏。

skill 全部改成 slash 指令

考慮了使用情境,與參考 Matt 的 skill 都是手動觸發,決定將我原本的有些會自己跑,都改成 slash 指令。

issue-orchestrate 也先退場,平行跑的部分改成用 /implement-spec(之後再考慮要不要加回來)。

risk 對齊 Matt 的 Door

在〈llm-wiki 從 log.md 到 git commit 的實作〉的結尾,我寫過第 3 輪交回的規則,不分這條規則是屏障還是提示。而 Matt 的 pr skill 本來就用 Door(單向門/雙向門)判斷一個改動錯了能不能收回,所以我的 risk 機制似乎可以切齊他的設置:用 Door 決定 risk label,也決定第 3 輪要不要交回。

risk-guard 是 PR 上的 CI 檢查,risk: low 的 PR 必須通過它才能自動 merge。

這樣連帶著思考 risk-guard 的存在必要性?包含其內部的白名單。因為導入這個白名單,就需要考慮 risk 與白名單之間誰的優先度較高,權衡下決定拿掉白名單,並且移除 risk: low 可以自動合併,全都不能自動合併,risk 標籤在於提醒使用者嚴重程度。

同時將 security review 換成 Cloudflare 的 security-audit:用 guidance mode,只有 confirmed 且中度以上才交回,強化建議不算問題,每個發現再交給另一個 subagent 嘗試推翻。

回頭看,〈把 issue 工作流制式化成共用 skill〉當時沒選的兩個選項,正是「只靠 AI 判斷後掛 label」和「不開 auto-merge」。

spec 與洩漏

spec 要放 issue 還是檔案,我本來想說其實沒差,結論也是沒差:spec 用完即丟,長期的決策靠 ADR。

grill 與審查

Plan 用 grill 強化。grill 一輪定下 10 題,統一在 session 裡決策、PR 是留下 security + code 每輪兩個 subagent、最多三輪的紀錄。

文章從開發的 session 開始,大綱併進 post-write

開發 -> 文章素材

這篇文章本身的素材也是一個例子,原本的 article-request 是從已 merge、帶 label 的 PR 交接,素材要寫在 PR 本文,將所有的資訊都預先在 github 寫完。基於上面所提到的主軸,順序如下:

  1. /implement, /issue-ship 後的 session
  2. 同 session 執行 article-request 整理資訊,直接丟上去部落格 repo。它不帶參數,從這次對話寫素材,直接放進部落格的文章卡。

文章素材 -> 文章

我原本以為文章的大綱是 article-request 寫的,結果是 issue-enhance。這樣就想說 issue-enhance 要退場,又想要在同一對話完成所有,所以就乾脆直接省略 /implement 這階段,直接用post-write,先寫大綱、在對話裡確認後再把文章順完。

文章相關的退場

原本的 issue-enhance 在實作已由 implement 取代,在文章端,也是擴充 post-write 來取代。所以不需要 原本的 article-status 關於 issue 開發的文章狀態,也不需要了,因為直接內部決定,並且上 issue

改動都是為了減輕化工作流的複雜程度。

實作

最後的形狀

一張 issue 從頭到尾的流程:Matt 的 skill 是主幹,kiwi 的 skill 穿插其中

  • Matt 的(不改):/triage、/to-spec、/to-tickets、/implement、/implement-spec、pr、code-review、grilling。
  • kiwi 留下的:
    • issue-ship(新):verify → pr 寫本文 → 每輪 security-audit 加 code-review 兩個新 subagent、最多三輪 → Door 決定 risk label → 不 merge。
    • issue-workflow-setup:縮小,接在 Matt 的 setup 之後。
    • article-request、post-write(多了大綱)、writing-voice(kiwi-voice 改名)。
  • 只能手動觸發:issue-ship、issue-workflow-setup、article-request、post-write。writing-voice 例外,因為 post-write 執行到一半要叫它兩次。
  • 共用 workflow:risk-guard 退場後從 4 支變 3 支,再退場 article-pending,剩 issue-status、needs-info-reply 2 支。
  • label:沿用 status:*,新增 status: ready-for-human、status: wontfix、type: bug、type: enhancement;status: ai-enhanced、grill 退場;GitHub 預設 label 不用。
  • agent 設定檔(docs/agents/):讀 issue 只讀 owner 的留言、列 issue 只列 owner 的、讀單張時先標出 external,防外人在已核准的 issue 底下貼假的 agent brief。
  • 文章:除了開發的對話,也可以從已 merge 的 PR 或一個檔案開始。隱私規則移到寫素材的地方:只引用作者自己的話、其他人的文字當成附出處的事實、公開目標不列私人來源(改寫成摘要)。

issue-ship 的 risk 與第 3 輪

issue-ship 的 risk 規則,第一個符合的就是答案:

1. **`risk: high`**: a one-way door, or a blast radius across repos.
2. **`risk: low`**: a two-way door, with nothing from the review left that keeps it off low (Step 4), no rule in the repo's `CLAUDE.md` that keeps it off low, and no change to the files that steer the workflow and the AI: anything under `.github/` or `.claude/`, a `CLAUDE.md`, or the agent docs `## Agent skills` points to.
3. **`risk: medium`**: everything else.

白名單拿掉之後,.github/、.claude/、CLAUDE.md 這幾個會改到 AI 行為的檔案,就寫在規則裡,不再靠路徑清單。

第 3 輪看 Door 決定要不要交回:

- **A medium or high finding remains that sits behind a one-way door** (for security, only a `confirmed` one): the approach is probably wrong. Don't patch it. Make the PR a draft and bring it to the author in the session: the findings grouped by category, why they point at the approach, and two or three other directions. They decide there whether to start over.
- **Anything else**: fix what you can, list the rest under Not verified as known gaps, and carry on.

這就是〈llm-wiki 從 log.md 到 git commit 的實作〉結尾想調整的那件事:雙向門後面的問題,修得掉就修,修不掉列成已知缺口,不再一律交回。

套到每個 repo

我希望每個 repo 都只留下一個 PR 給我來做事,issue 也一起調整完:

  • repair 3 個:部落格、llm-wiki、coffee-review。coffee-review 開著「Allow auto-merge」和 required check,由我自己去設定裡拿掉。
  • 新裝 4 個:art-tracking、travel-storage、food-picker、daily-tracking,其中 art-tracking、food-picker、daily-tracking 原本的 issue 一起標上 status: 進流程。travel-storage 的行程內容照舊直接進 main,其他才走 issue 流程。
  • 加上 skills 自己,全部由我 merge。

文章 label 退場的那次 repair,7 個 repo 交給 7 個 subagent 平行跑,每個 repo 一個 PR,全部 risk: high。

數字

  • 換底層那次:7 個 commit、34 個檔案、+1059/−2150;退場 3 個 skill,新增 1 個。
  • issue-workflow-setup:476 行,退場 risk-guard 時 399 行,換底層那次結束時 410 行。
  • 審查:code-review 共 40 項、安全 13 項(細節不公開)。
  • 文章 label 退場那次:4 個 commit、14 個檔案、+187/−293;三輪審查,code-review 7、5、5 項,安全 2 項,沒有交回。
  • 共用 workflow:4 → 3 → 2;每個 repo 的 label:14 → 11。

踩到的坑

.claude/agents/

這是 subagent 的資料夾

一開始建議把 llm-wiki vault 的 agent 設定檔放 .claude/agents/,第一輪 code-review 才發現,Claude Code 會把那裡的檔案當成 subagent 載入。vault 的根目錄只放結構檔,vault 也會被發布,所以改成 .claude/agent-docs/。

審查只跑了一輪

收尾前才想到:這次也有跑三輪嗎?結果沒有,只跑了一輪 code-review,於是照新規則補跑。

權限擋下

  • 移除 required check 被判定為「CI Bypass」擋下,最後由我自己跑指令。
  • 一個 subagent 的 brief 也因為含這一步被擋,拿掉後重派。
  • 開 PR 時,說明裡寫到私人 repo 的名稱被擋,刪掉那句才開成。
  • 文章 label 退場時,7 個 repo 平行交給 subagent,其中兩個刪 article-status: label 時被權限檢查擋住。主 session 沒有代刪,那等於繞過權限,改成列給我自己執行。

沒有 authorAssociation

讀單張 issue 時要標出不是 owner 開的,但 gh issue view 沒有 authorAssociation 欄位,只能改比對 author.login。

補 commit 前就進了 main

deny 規則推上分支時,llm-wiki 和 coffee-review 的改動已經進了 main,只好再各補一次。

沒關的程式碼區塊

最後一輪審查抓到一個 high:issue-workflow-setup Step 9 的 code fence 後面接了文字,整段說明被吞進程式碼區塊。雙向門,直接修。

settings.local.json

food-picker 是公開 repo,裡面 commit 了含本機路徑的 .claude/settings.local.json。改成停止追蹤、加進 .gitignore,不改寫歷史:16 個 PR 參照會留住舊 commit,改寫也清不乾淨。

漏掉已關閉的 issue

刪文章 label 前,會先列出「還在等文章」的項目,但迴圈只查 open 的 issue 和 merged 的 PR。coffee-review 有一張 issue 被 merge 的 PR 關掉,label 留在 issue 上、PR 上沒有,所以列不到。

是負責那個 repo 的 subagent 多查一步才發現,label 先留著。之後改成 issue 查全部狀態,並附上關掉它的 PR 編號,正好是 /article-request <PR> 要的參數。

小結

現在的流程,步驟越來越精鍊:triage、spec、tickets、implement 交給 Matt 的 skill,我只留下出貨(issue-ship)、文章(article-request、post-write、writing-voice)和安裝(issue-workflow-setup)。沒有任何自動 merge,每個 PR 都由我 merge,risk: 標籤在於提醒我嚴重程度。

我也盡量把內容從 GitHub 拉回到 session 內部:

  1. 文章大綱在對話裡確認。
  2. 每輪審查的結果在 session 裡決策,PR 只留紀錄。
  3. 文章素材直接從開發的對話寫。

把笨重的東西都捨棄,回歸到 spec 到 implement 這個概念,然後站在巨人 Matt 的肩膀上。