diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 2c2dafc..e0e3f53 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1,11 +1,11 @@ { - "name": "template", + "name": "herdr-issue", "plugins": [ { - "name": "jsc-template", + "name": "herdr-issue", "source": { "source": "url", - "url": "https://gitea.jsc.idv.tw/plugins/template.git" + "url": "https://gitea.jsc.idv.tw/plugins/herdr-issue.git" } } ] diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e3d2436..06a1e65 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,14 +1,14 @@ { - "name": "template", - "description": "JSC 跨 AI 助理共用 skills 的 Claude Code marketplace。", + "name": "herdr-issue", + "description": "herdr-issue 的 Claude Code marketplace。", "owner": { "name": "JSC" }, "plugins": [ { - "name": "jsc-template", + "name": "herdr-issue", "source": "./", - "description": "JSC 共用 skills(跨 AI 助理)" + "description": "herdr-issue skill" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index a5c2dd3..2b8a00d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,12 +1,12 @@ { - "name": "jsc-template", - "version": "0.0.3", - "description": "JSC 跨 AI 助理共用 plugin 模板(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-template: 前綴呼叫。", + "name": "herdr-issue", + "version": "0.1.0", + "description": "透過 Herdr 讓 Claude agent 逐一實作 Gitea 議題(每個議題一個 worktree 與 tab)。", "skills": "./skills", "author": { "name": "JSC" }, - "homepage": "https://gitea.jsc.idv.tw/plugins/template", - "repository": "https://gitea.jsc.idv.tw/plugins/template.git", - "keywords": ["template", "skills", "cross-tool", "jsc"] + "homepage": "https://gitea.jsc.idv.tw/plugins/herdr-issue", + "repository": "https://gitea.jsc.idv.tw/plugins/herdr-issue.git", + "keywords": ["herdr", "gitea", "issue", "skills", "jsc"] } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 71a3c32..8d9db05 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { - "name": "jsc-template", - "version": "0.0.3", - "description": "JSC 跨 AI 助理共用 plugin 模板。所有 skills 以 SKILL.md 為共通標準。", + "name": "herdr-issue", + "version": "0.1.0", + "description": "透過 Herdr 讓 Claude agent 逐一實作 Gitea 議題(每個議題一個 worktree 與 tab)。", "skills": "./skills" } diff --git a/AGENTS.md b/AGENTS.md index a86a1f1..ab7d9da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# jsc-template — 共用 Skills(跨 AI 助理) +# herdr-issue — 共用 Skills(跨 AI 助理) 本 repo 是一組以 **Agent Skills(`SKILL.md`)** 標準撰寫的共用 skills,可同時被 Claude Code、Codex、Antigravity、OpenCode 使用。 @@ -6,7 +6,7 @@ - 所有可用的 skills 位於本 repo 的 `skills//SKILL.md`。 - 在處理任務前,先比對使用者需求與各 skill `SKILL.md` frontmatter 的 `description`,若相符請載入並依其步驟執行。 -- **呼叫慣例**:在 Claude Code 與 Antigravity 中,這些 skill 以 `/jsc-template:` 呼叫;Codex 以 `$`、OpenCode 由模型依描述自動觸發 — 兩者沒有 `/jsc-template:` 前綴,不需強制加。 +- **呼叫慣例**:在 Claude Code 與 Antigravity 中,這些 skill 以 `/herdr-issue:` 呼叫;Codex 以 `$`、OpenCode 由模型依描述自動觸發 — 兩者沒有 `/herdr-issue:` 前綴,不需強制加。 - 完整清單與每個 skill 的用途,請見 `README.md` 的「Skills 目錄」。 ## 慣例 diff --git a/README.md b/README.md index 6c6669e..620c7c2 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# jsc-template — 跨 AI 助理 Plugin 模板 +# herdr-issue — 用 Herdr 驅動 Gitea 議題 一個可同時被 **Claude Code、Codex、Antigravity、OpenCode、GitHub Copilot** 安裝的 plugin 模板。 核心是以 [Agent Skills(`SKILL.md`)](https://agentskills.io) 標準撰寫的共用 skills(唯一真實來源放在 `skills/`), 搭配各助理各自的 plugin manifest,讓**同一個 repo** 可用各家**原生 plugin CLI** 安裝。 -在 Claude Code 與 Antigravity 中,skill 以 **`/jsc-template:` 前綴**呼叫(例如 `/jsc-template:hello`)。 +在 Claude Code 與 Antigravity 中,skill 以 **`/herdr-issue:` 前綴**呼叫(例如 `/herdr-issue:herdr-issue`)。 --- ## 前綴與呼叫方式 -| 助理 | 安裝方式 | 呼叫 | `/jsc-template:` 前綴 | +| 助理 | 安裝方式 | 呼叫 | `/herdr-issue:` 前綴 | | --- | --- | --- | --- | -| Claude Code | `claude plugin`(marketplace) | `/jsc-template:` 或自動觸發 | ✅ | +| Claude Code | `claude plugin`(marketplace) | `/herdr-issue:` 或自動觸發 | ✅ | | Codex | `codex plugin`(marketplace) | `$` 或 `/skills` 選單 | ❌(用 `$name`) | -| Antigravity | `agy plugin install` | `/jsc-template:` 或自動觸發 | ✅ | +| Antigravity | `agy plugin install` | `/herdr-issue:` 或自動觸發 | ✅ | | OpenCode | skills 目錄(複製/clone) | 描述需求自動觸發 | ❌(依名稱) | -| GitHub Copilot CLI | `copilot plugin`(marketplace) | 自然語言或 plugin skills | ❌(無 `/jsc-template:` 前綴) | +| GitHub Copilot CLI | `copilot plugin`(marketplace) | 自然語言或 plugin skills | ❌(無 `/herdr-issue:` 前綴) | > Codex 不支援自訂前綴(skill 以 `$name` 呼叫);OpenCode 由模型依描述自動呼叫;Copilot CLI 透過原生 plugin 安裝後以自然語言或 plugin skills 使用。三者皆**不強制**前綴。 @@ -26,17 +26,17 @@ 同一個 repo 同時帶四種 manifest,彼此以路徑隔離、互不干擾;各助理都讀同一份 `skills/`。 ``` -template/ +herdr-issue/ ├── .claude-plugin/ -│ ├── plugin.json # Claude 外掛定義(name: "jsc-template") -│ └── marketplace.json # Claude marketplace(name: "template",source 指向本 repo) +│ ├── plugin.json # Claude 外掛定義(name: "herdr-issue") +│ └── marketplace.json # Claude marketplace(name: "herdr-issue",source 指向本 repo) ├── .codex-plugin/ -│ └── plugin.json # Codex 外掛定義(name: "jsc-template",skills: "./skills") +│ └── plugin.json # Codex 外掛定義(name: "herdr-issue",skills: "./skills") ├── .agents/plugins/ -│ └── marketplace.json # Codex marketplace(name: "template",url source 指向本 repo) -├── plugin.json # Antigravity 外掛定義(name: "jsc-template",skills: "./skills/") +│ └── marketplace.json # Codex marketplace(name: "herdr-issue",url source 指向本 repo) +├── plugin.json # Antigravity 外掛定義(name: "herdr-issue",skills: "./skills/") ├── skills/ # ★ 唯一真實來源:所有 skills -│ └── hello/SKILL.md +│ └── herdr-issue/SKILL.md ├── AGENTS.md # 跨助理共用指引 └── README.md ``` @@ -45,64 +45,50 @@ template/ ## 安裝 / 更新 / 移除(各助理) -> 指令中的 repo 網址換成你的:`https://gitea.jsc.idv.tw/plugins/template.git` +> 指令中的 repo 網址換成你的:`https://gitea.jsc.idv.tw/plugins/herdr-issue.git` > > **Claude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repo `push` 到 gitea。** > **Antigravity 的 `agy plugin install ` 目前只支援 github.com**;gitea 請改用「clone + 本地路徑」(見 Antigravity 節)。 > 本機/離線:Claude 可用本地路徑加 marketplace;Antigravity 用本地路徑安裝。 -> **⚠ 從 0.0.1 升上來的人請先移除再安裝。** 0.0.2 起 marketplace 名由 `jsc-plugins` 改為 `template` -> (舊名與 `jsc-persona` 的 marketplace 撞名)。各助理以 `@` 當安裝識別鍵, -> 改名後是兩筆獨立條目,**不能用 update 遷移**: -> -> ```bash -> claude plugin uninstall jsc-template@jsc-plugins -> claude plugin marketplace remove jsc-plugins # 若你沒有裝 jsc-persona 才移除,它也用這個名字 -> claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/template.git -> claude plugin install jsc-template@template -> ``` -> -> 移除後記得清掉 `~/.claude/settings.json` 裡 `enabledPlugins` 的舊鍵 `jsc-template@jsc-plugins`, -> 並重開工作階段。Codex/Copilot 同理(`remove`/`uninstall` 後重新 `add`+`install`)。 - ### Claude Code ```bash # 安裝 -claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/template.git -claude plugin install jsc-template@template +claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/herdr-issue.git +claude plugin install herdr-issue@herdr-issue # 更新 -claude plugin marketplace update template -claude plugin update jsc-template@template +claude plugin marketplace update herdr-issue +claude plugin update herdr-issue@herdr-issue # 移除 -claude plugin uninstall jsc-template@template -claude plugin marketplace remove template +claude plugin uninstall herdr-issue@herdr-issue +claude plugin marketplace remove herdr-issue ``` - 工作階段內 slash 版(等價):把 `claude plugin` 換成 `/plugin`。 -- 本機開發(免 push):`claude plugin marketplace add C:\Users\h3285\source\repos.plugins\template`(本地路徑)後再 install。 -- **呼叫**:`/jsc-template:`(例 `/jsc-template:hello`)。 +- 本機開發(免 push):`claude plugin marketplace add <本地路徑>` 後再 install。 +- **呼叫**:`/herdr-issue:`(例 `/herdr-issue:herdr-issue`)。 ### Codex ```bash # 安裝 -codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/template.git -codex plugin add jsc-template@template +codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/herdr-issue.git +codex plugin add herdr-issue@herdr-issue # 更新(重新抓取 marketplace 的 git 快照) -codex plugin marketplace upgrade template +codex plugin marketplace upgrade herdr-issue # 移除 -codex plugin remove jsc-template@template -codex plugin marketplace remove template +codex plugin remove herdr-issue@herdr-issue +codex plugin marketplace remove herdr-issue ``` -- 安裝 token `jsc-template@template` = plugin 名(`.codex-plugin/plugin.json` 的 `name`)@ marketplace 名(`.agents/plugins/marketplace.json` 的 `name`)。 +- 安裝 token `herdr-issue@herdr-issue` = plugin 名(`.codex-plugin/plugin.json` 的 `name`)@ marketplace 名(`.agents/plugins/marketplace.json` 的 `name`)。 - 本 repo 的 Codex marketplace 以 `url` 來源指向自己,故 Codex **一律從 gitea 安裝**(需先 push);安裝後重啟 Codex。 -- **呼叫**:`$`(例 `$hello`),或用 `/skills` 選單。 +- **呼叫**:`$`(例 `$herdr-issue`),或用 `/skills` 選單。 ### Antigravity(`agy`) @@ -110,21 +96,21 @@ codex plugin marketplace remove template ```bash # 安裝:clone 後用本地路徑 -git clone https://gitea.jsc.idv.tw/plugins/template.git ~/plugins/template -agy plugin install ~/plugins/template +git clone https://gitea.jsc.idv.tw/plugins/herdr-issue.git ~/plugins/herdr-issue +agy plugin install ~/plugins/herdr-issue # 更新(agy 無 update 子指令 → git pull 後重裝) -git -C ~/plugins/template pull -agy plugin uninstall jsc-template -agy plugin install ~/plugins/template +git -C ~/plugins/herdr-issue pull +agy plugin uninstall herdr-issue +agy plugin install ~/plugins/herdr-issue # 移除 -agy plugin uninstall jsc-template +agy plugin uninstall herdr-issue ``` - 若把 skills 放到 GitHub,則可直接 `agy plugin install https://github.com//`。 -- 其他:`agy plugin list`、`agy plugin enable jsc-template` / `disable jsc-template`、`agy plugin validate `。安裝後重啟工作階段。 -- **呼叫**:`/jsc-template:`(例 `/jsc-template:hello`)或依描述自動觸發。 +- 其他:`agy plugin list`、`agy plugin enable herdr-issue` / `disable herdr-issue`、`agy plugin validate `。安裝後重啟工作階段。 +- **呼叫**:`/herdr-issue:`(例 `/herdr-issue:herdr-issue`)或依描述自動觸發。 ### OpenCode @@ -133,16 +119,16 @@ OpenCode 會讀 `~/.config/opencode/skills/`(也會讀 `~/.claude/skills/`、` ```bash # 安裝 -git clone https://gitea.jsc.idv.tw/plugins/template.git ~/plugins/template +git clone https://gitea.jsc.idv.tw/plugins/herdr-issue.git ~/plugins/herdr-issue mkdir -p ~/.config/opencode/skills -cp -r ~/plugins/template/skills/* ~/.config/opencode/skills/ +cp -r ~/plugins/herdr-issue/skills/* ~/.config/opencode/skills/ # 更新 -git -C ~/plugins/template pull -cp -r ~/plugins/template/skills/* ~/.config/opencode/skills/ +git -C ~/plugins/herdr-issue pull +cp -r ~/plugins/herdr-issue/skills/* ~/.config/opencode/skills/ # 移除 -rm -rf ~/.config/opencode/skills/hello +rm -rf ~/.config/opencode/skills/herdr-issue ``` > **Windows PowerShell**:`cp -r A B` → `Copy-Item A B -Recurse -Force`、`rm -rf X` → `Remove-Item X -Recurse -Force`、`~` → `$HOME`。 @@ -155,21 +141,21 @@ Copilot CLI 支援與 Claude Code 類似的原生 plugin / marketplace 指令, ```bash # 安裝 -copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/template.git -copilot plugin install jsc-template@template +copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/herdr-issue.git +copilot plugin install herdr-issue@herdr-issue # 更新 -copilot plugin marketplace update template -copilot plugin update jsc-template@template +copilot plugin marketplace update herdr-issue +copilot plugin update herdr-issue@herdr-issue # 移除 -copilot plugin uninstall jsc-template@template -copilot plugin marketplace remove template +copilot plugin uninstall herdr-issue@herdr-issue +copilot plugin marketplace remove herdr-issue ``` -- 安裝 token `jsc-template@template` = plugin 名(plugin manifest 的 `name`)@ marketplace 名。 +- 安裝 token `herdr-issue@herdr-issue` = plugin 名(plugin manifest 的 `name`)@ marketplace 名。 - `copilot plugin marketplace add` 支援 GitHub `owner/repo`、git URL 與本地路徑;Gitea repo 可用上方 HTTPS URL。 -- **呼叫**:在 Copilot CLI 中用自然語言描述需求,例如 `copilot -i "跑 hello 確認 plugin 裝好了"`。 +- **呼叫**:在 Copilot CLI 中用自然語言描述需求,例如 `copilot -i "跑 herdr-issue 確認 plugin 裝好了"`。 --- @@ -177,18 +163,18 @@ copilot plugin marketplace remove template 安裝好之後,不必進互動介面,一行指令就能叫某個 skill 跑完並印出結果: -| 助理 | headless 指令 | 執行 `hello` skill | +| 助理 | headless 指令 | 執行 `herdr-issue` skill | | --- | --- | --- | -| Claude Code | `claude -p ""` | `claude -p "/jsc-template:hello"` | -| Codex | `codex exec ""` | `codex exec '$hello'` | -| Antigravity | `agy -p ""` | `agy -p "/jsc-template:hello"` | -| OpenCode | `opencode run ""` | `opencode run "用 hello skill 打個招呼"` | -| GitHub Copilot CLI | `copilot -p ""` | `copilot -p "用 hello skill 打個招呼"` | +| Claude Code | `claude -p ""` | `claude -p "/herdr-issue:herdr-issue"` | +| Codex | `codex exec ""` | `codex exec '$herdr-issue'` | +| Antigravity | `agy -p ""` | `agy -p "/herdr-issue:herdr-issue"` | +| OpenCode | `opencode run ""` | `opencode run "用 herdr-issue skill 打個招呼"` | +| GitHub Copilot CLI | `copilot -p ""` | `copilot -p "用 herdr-issue skill 打個招呼"` | -- Claude / Antigravity 支援 `/jsc-template:` 前綴,直接 `-p "/jsc-template:"` 即可。 -- Codex 以 `$` 觸發;在 shell 請用**單引號**避免 `$` 被展開:`codex exec '$hello'`。 +- Claude / Antigravity 支援 `/herdr-issue:` 前綴,直接 `-p "/herdr-issue:"` 即可。 +- Codex 以 `$` 觸發;在 shell 請用**單引號**避免 `$` 被展開:`codex exec '$herdr-issue'`。 - OpenCode 與 Copilot 沒有前綴,用自然語言描述需求;Copilot CLI 會讀取已安裝 plugin 提供的 skills。 -- 帶引數就接在後面,例如 `claude -p "/jsc-template:hello 參數"`、`codex exec '$hello 參數'`。 +- 帶引數就接在後面,例如 `claude -p "/herdr-issue:herdr-issue 參數"`、`codex exec '$herdr-issue 參數'`。 --- @@ -199,13 +185,12 @@ copilot plugin marketplace remove template -### `hello` +### `herdr-issue` -範例 skill,用來驗證 jsc plugin 是否安裝成功,也是新增 skill 的範本。當使用者輸入 hello、想測試 plugin、或想看 skill 模板長什麼樣子時觸發;回覆一句問候並簡述此 plugin 的用途。 +透過 Herdr 讓 Claude agent 逐一實作 Gitea 議題:每個議題一個 worktree 與 tab,監看到議題關閉後清理,並推薦下一批可執行的議題。僅在使用者執行 `/herdr-issue` 或明確要求以 Herdr 驅動 Gitea 議題/repo 連結時觸發。需在 Herdr 內(`HERDR_ENV=1`)執行。 -- **Claude Code / Antigravity**:`/jsc-template:hello` -- **Codex**:`$hello`,或用 `/skills` 選單 -- **OpenCode / GitHub Copilot CLI**:描述需求自動觸發 +- **Claude Code**:`/herdr-issue:herdr-issue <議題或 repo 連結>` +- **skills CLI**:`npx skills add https://gitea.jsc.idv.tw/plugins/herdr-issue.git --skill herdr-issue -g`,之後 `/herdr-issue <連結>` @@ -213,16 +198,16 @@ copilot plugin marketplace remove template ## 新增一個 skill -1. 複製範本:`cp -r skills/hello skills/` +1. 複製範本:`cp -r skills/herdr-issue skills/` 2. 編輯 `skills//SKILL.md` 的 frontmatter: - - `name`:小寫、數字、連字號(`-`),最長 64 字元。**這就是 Claude Code / Antigravity 的 `/jsc-template:`**。 + - `name`:小寫、數字、連字號(`-`),最長 64 字元。**這就是 Claude Code / Antigravity 的 `/herdr-issue:`**。 - `description`:第三人稱,寫清楚「何時用、何時不用」與觸發關鍵字 — 這是各助理自動載入的唯一依據。 3. 在內文寫下 skill 的具體步驟。 4. 手動把這個 skill 補進上方「Skills 目錄」區塊。 5. **bump 版本並 push**:各助理都以 git 內容/版本判斷更新,請把 `.claude-plugin/plugin.json`、`.codex-plugin/plugin.json`、`plugin.json` 三個 manifest 的 `version` 一起 bump,commit 後 push 到 gitea。 6. 讓各助理更新: - - Claude:`claude plugin update jsc-template@template` - - Codex:`codex plugin marketplace upgrade template` - - Antigravity:`git -C ~/jsc-plugin pull && agy plugin uninstall jsc-template && agy plugin install ~/jsc-plugin` + - Claude:`claude plugin update herdr-issue@herdr-issue` + - Codex:`codex plugin marketplace upgrade herdr-issue` + - Antigravity:`git -C ~/jsc-plugin pull && agy plugin uninstall herdr-issue && agy plugin install ~/jsc-plugin` - OpenCode:`git pull` 後重新複製 `skills/` - - Copilot:`copilot plugin marketplace update template && copilot plugin update jsc-template@template` + - Copilot:`copilot plugin marketplace update herdr-issue && copilot plugin update herdr-issue@herdr-issue` diff --git a/plugin.json b/plugin.json index 4873395..3aab1df 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { - "name": "jsc-template", - "version": "0.0.3", - "description": "JSC 跨 AI 助理共用 plugin 模板。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-template: 前綴呼叫。", + "name": "herdr-issue", + "version": "0.1.0", + "description": "透過 Herdr 讓 Claude agent 逐一實作 Gitea 議題(每個議題一個 worktree 與 tab)。", "skills": "./skills/" } \ No newline at end of file diff --git a/skills/hello/SKILL.md b/skills/hello/SKILL.md deleted file mode 100644 index c5be22f..0000000 --- a/skills/hello/SKILL.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: hello -description: 範例 skill,用來驗證 jsc plugin 是否安裝成功,也是新增 skill 的範本。當使用者輸入 hello、想測試 plugin、或想看 skill 模板長什麼樣子時觸發;回覆一句問候並簡述此 plugin 的用途。 ---- - -# hello(範例 skill) - -這是 `jsc-template` plugin 的範例 skill。它有兩個用途: - -1. **驗證安裝** — 跨各家 AI 助理確認 skill 已被正確載入。 -2. **作為範本** — 複製這個資料夾即可新增一個新的 skill。 - -## 呼叫方式 - -| 助理 | 呼叫方式 | -| --- | --- | -| Claude Code | `/jsc-template:hello` | -| Antigravity | `/jsc-template:hello`,或描述需求自動觸發 | -| Codex | 在提示詞輸入 `$hello`,或用 `/skills` 選單 | -| OpenCode | 直接描述需求,模型會透過 skill 工具自動呼叫 | -| GitHub Copilot CLI | 直接描述需求,Copilot 會讀取已安裝 plugin 的 skills | - -## 行為 - -當這個 skill 被觸發時: - -1. 回覆「Hello from **jsc** 👋」。 -2. 用一句話說明 `jsc-template` 是一個跨 AI 助理的共用 skill 集合。 -3. 提示使用者可以在 README 的「Skills 目錄」查看所有可用的 skills。 - -## 如何以此為範本新增 skill - -1. 複製 `skills/hello/` 為 `skills//`。 -2. 修改 `SKILL.md` 的 frontmatter: - - `name`:小寫、數字、連字號(`-`),最長 64 字元。**這個名稱會成為 Claude Code / Antigravity 的 `/jsc-template:` 指令**。 - - `description`:第三人稱,寫清楚「什麼時候該用、什麼時候不該用」與觸發關鍵字 — 各家助理靠這段文字決定是否自動載入。 -3. 在內文寫下 skill 的具體步驟。 -4. 手動把新 skill 補進 README 的「Skills 目錄」區塊。 diff --git a/skills/herdr-issue/SKILL.md b/skills/herdr-issue/SKILL.md new file mode 100644 index 0000000..44cff98 --- /dev/null +++ b/skills/herdr-issue/SKILL.md @@ -0,0 +1,54 @@ +--- +name: herdr-issue +description: "Run Gitea issues through Herdr: one Claude agent per issue in its own worktree and tab, watch the issue to closure, then offer the next unblocked issues. Use only when the user runs /herdr-issue or explicitly asks to drive a Gitea issue or repo link with Herdr." +--- + +# herdr-issue + +把 Gitea 議題交給 Herdr 裡的 Claude agent 實作。所有確定性的工作都在 `scripts/` 裡,你只在「要做決定」的時刻出手。腳本都要在該 repo 的 clone 裡執行;`S` 代表 `~/.claude/skills/herdr-issue/scripts`。 + +## 語言規則 + +本技能的所有回覆、提問、通知與輸出訊息一律使用繁體中文(程式碼、指令、識別字除外)。 + +## 前置檢查 + +1. `test "${HERDR_ENV:-}" = 1`,不成立就說明不在 Herdr 裡並停止。 +2. 解析輸入: + - 議題連結 `https://gitea.jsc.idv.tw///issues/` → 流程 A。 + - `#N` → 用目前 clone 的 origin 推出 owner/repo,流程 A。 + - repo 連結 `https://gitea.jsc.idv.tw//` → 流程 B。 +3. 目前目錄必須是該 repo 的 clone(腳本會比對 origin)。不是就告訴使用者要在哪個目錄執行並停止。 +4. 提醒一句:這個 session 要保持開著,監看與下一步推薦都靠它。 + +## 流程 A:啟動一個議題 + +1. `S/start.sh `。它會建 worktree `../-issue-N`(分支已存在就接續)、開 tab `#N`、啟動 `issue-N` agent、送提示詞(`scripts/prompt.txt`)、掛 `in-progress` 標籤。最後一行是 JSON。失敗訊息在 stderr,照實轉述。 +2. 在**背景**執行 `S/watch-issue.sh `(Bash 的 `run_in_background`)。它會阻塞到議題關閉,期間 agent blocked 或消失會彈 Herdr 通知;關閉後自己關 tab、清 worktree 與分支、拿掉標籤、往上檢查規格與總覽能不能關。結束時你會被叫醒。 +3. 做一次「推薦下一步」。 + +## 流程 B:從 repo 挑議題 + +1. `S/ready.sh `。 +2. 用多選問題列出「可執行」的議題,每項標題帶規格標籤;同一規格的議題在描述裡註明「同規格,PR 可能互相衝突」。「掛 in-progress 但沒有 agent」的議題另列一題,說明重跑會接續先前分支。 +3. 勾選的每一個議題各走流程 A 的第 1、2 步,全部啟動後做一次「推薦下一步」。 + +## 推薦下一步 + +`S/ready.sh ` 再跑一次(剛啟動的議題已經在執行中,會被排除),結果用多選問題呈現,格式同流程 B。沒勾就什麼都不做。 + +## 被 watch-issue.sh 叫醒時 + +把它印出的清理摘要(PR 狀態、分支去留、規格與總覽是否關閉)用兩三句轉述,然後「推薦下一步」。 + +## 腳本一覽 + +| 腳本 | 給誰 | 做什麼 | +| --- | --- | --- | +| `ready.sh` | 你 | 列出可執行、執行中、被阻擋的議題 | +| `start.sh` | 你 | 建 worktree、開 tab、啟動 agent、送提示詞、掛標籤 | +| `watch-issue.sh` | 你(背景) | 等議題關閉,清理,往上關規格與總覽 | +| `tick.sh` | 子 agent | 勾議題內文第 k 個核取方塊 | +| `pr-wait.sh` | 子 agent(背景) | 等 PR 衝突、關閉或合併 | + +環境變數:`HERDR_ISSUE_INTERVAL`(pr-wait 輪詢秒數,預設 300)、`HERDR_ISSUE_WATCH_INTERVAL`(watch-issue 輪詢秒數,預設 5)、`HERDR_ISSUE_BLOCKED_AFTER`(blocked 多久才通知,預設 60 秒)、`HERDR_ISSUE_MODEL`(預設 `claude-opus-5-5`)。 diff --git a/skills/herdr-issue/scripts/lib.sh b/skills/herdr-issue/scripts/lib.sh new file mode 100755 index 0000000..675a18e --- /dev/null +++ b/skills/herdr-issue/scripts/lib.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# herdr-issue 共用函式。每個腳本都 source 這個檔。 +set -euo pipefail + +LABEL="in-progress" +LABEL_COLOR="0e8a16" +LABEL_DESC="Herdr 的 agent 正在實作" +INTERVAL="${HERDR_ISSUE_INTERVAL:-300}" +MODEL="${HERDR_ISSUE_MODEL:-claude-opus-5-5}" +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +die() { echo "herdr-issue: $*" >&2; exit 1; } + +# 從 stdin 讀 JSON,執行 python 片段,d 是解析後的物件。 +jq_py() { python3 -c "import sys,json +def find(o,k): + if isinstance(o,dict): + if k in o: return o[k] + for v in o.values(): + r=find(v,k) + if r is not None: return r + elif isinstance(o,list): + for v in o: + r=find(v,k) + if r is not None: return r + return None +d=json.load(sys.stdin) +$1"; } + +# 議題的完整 JSON(state、labels、body、title、closedAt)。 +issue_json() { tea issues "$2" --repo "$1" --output json; } + +issue_state() { issue_json "$1" "$2" | jq_py 'print(d["state"])'; } + +issue_labels() { issue_json "$1" "$2" | jq_py 'print("\n".join(l["name"] for l in d.get("labels") or []))'; } + +# 某個標籤底下還有幾個 open 議題。 +open_count_with_label() { + tea issues list --repo "$1" --state open --labels "$2" --limit 500 --output json --fields index \ + | jq_py 'print(len(d))' +} + +ensure_label() { + if ! tea labels list --repo "$1" --output json | jq_py "sys.exit(0 if any(l['name']=='$LABEL' for l in d) else 1)"; then + tea labels create --repo "$1" --name "$LABEL" --color "$LABEL_COLOR" --description "$LABEL_DESC" >/dev/null + fi +} + +# agent 狀態:idle|working|blocked|done|unknown|missing +agent_status() { + herdr agent get "issue-$1" 2>/dev/null | jq_py 'print(find(d,"agent_status") or "unknown")' 2>/dev/null || echo missing +} + +# 標籤為 #N 的 tab ID,沒有就空字串。 +tab_for_issue() { + herdr tab list --workspace "${HERDR_WORKSPACE_ID:?}" \ + | jq_py "print(next((t['tab_id'] for t in find(d,'tabs') or [] if t.get('label')=='#$1'),''))" +} + +# worktree 路徑:clone 同層的 -issue-N +worktree_path() { echo "$(dirname "$2")/$(basename "$2")-issue-$1"; } + +notify() { herdr notification show "$1" --body "$2" --sound "${3:-request}" >/dev/null 2>&1 || true; } + +# 目前目錄必須是 owner/repo 的 clone;印出 clone 根目錄。 +clone_root_for() { + local root url + root="$(git rev-parse --show-toplevel 2>/dev/null)" || die "目前目錄不是 git repo,請在 $1 的 clone 裡執行" + url="$(git -C "$root" remote get-url origin 2>/dev/null || true)" + case "$url" in + *"$1"|*"$1.git"|*"$1/") echo "$root" ;; + *) die "目前 clone 的 origin 是 $url,不是 $1" ;; + esac +} + +# head 分支為 issue-N 的最新 PR:印出「index state」或空字串。 +pr_for_issue() { + tea pulls list --repo "$1" --state all --limit 500 --output json --fields index,state,mergeable,head \ + | jq_py "m=[p for p in d if p.get('head')=='issue-$2'] +print(' '.join([m[0]['index'],m[0]['state'],str(m[0].get('mergeable'))]) if m else '')" +} diff --git a/skills/herdr-issue/scripts/pr-wait.sh b/skills/herdr-issue/scripts/pr-wait.sh new file mode 100755 index 0000000..354bebe --- /dev/null +++ b/skills/herdr-issue/scripts/pr-wait.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# 用法:pr-wait.sh +# 阻塞到 head 為 issue-N 的 PR 有衝突、被關閉或合併,或議題被關閉。 +# 最後一行印出原因:conflict | closed | merged | issue-closed +source "$(dirname "$0")/lib.sh" +REPO="${1:?}"; N="${2:?用法:pr-wait.sh }" +while :; do + read -r PR STATE MERGEABLE <<<"$(pr_for_issue "$REPO" "$N")" || true + if [ -z "${PR:-}" ]; then + echo "還沒有 head 為 issue-$N 的 PR,$INTERVAL 秒後再查" + else + case "$STATE" in + merged) echo "PR #$PR 已合併"; echo merged; exit 0 ;; + closed) echo "PR #$PR 被關閉(未合併)"; echo closed; exit 0 ;; + open) + if [ "$MERGEABLE" = "false" ]; then + sleep 60 + read -r _ _ AGAIN <<<"$(pr_for_issue "$REPO" "$N")" + if [ "$AGAIN" = "false" ]; then echo "PR #$PR 與 main 有衝突"; echo conflict; exit 0; fi + fi ;; + esac + fi + if [ "$(issue_state "$REPO" "$N")" = "closed" ]; then echo "議題 #$N 已關閉"; echo issue-closed; exit 0; fi + sleep "$INTERVAL" +done diff --git a/skills/herdr-issue/scripts/prompt.txt b/skills/herdr-issue/scripts/prompt.txt new file mode 100644 index 0000000..96abd17 --- /dev/null +++ b/skills/herdr-issue/scripts/prompt.txt @@ -0,0 +1,17 @@ +/mattpocock-skills:implement {LINK} + +你在分支 issue-{N} 的 worktree 裡,從 origin/main 建立,請在這裡實作,不要另外建 worktree。 +{RESUME} +規則: +0. 所有回覆、commit 訊息、PR 標題與描述、議題留言一律使用繁體中文(程式碼與指令除外)。 +1. 議題的代辦項目是內文 Acceptance criteria 的核取方塊。每完成一項就執行 + {S}/tick.sh {REPO} {N} <第幾項>(從第一個核取方塊數起,已勾的也算) + 它會重新讀取最新內文再改,不要自己用 tea issues edit 重寫整段。 +2. 全部完成後推送分支並建立 PR 回 main: + tea pulls create --repo {REPO} --base main --head issue-{N} --title "<標題>" --description "<摘要> + +Closes #{N}" +3. 然後在背景執行 {S}/pr-wait.sh {REPO} {N},它會在 PR 有衝突、被關閉或合併時結束並印出原因: + - conflict:git fetch origin && git merge origin/main,用 /mattpocock-skills:resolving-merge-conflicts 解決,跑測試,推送,再執行一次 pr-wait.sh。 + - closed:tea issues close --repo {REPO} {N},結束。 + - merged 或 issue-closed:結束。 diff --git a/skills/herdr-issue/scripts/ready.py b/skills/herdr-issue/scripts/ready.py new file mode 100755 index 0000000..26fdd0b --- /dev/null +++ b/skills/herdr-issue/scripts/ready.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""列出可執行的議題。輸入:open.json agents.json tabs.json""" +import json, re, sys + +open_issues = json.load(open(sys.argv[1])) +agents = json.load(open(sys.argv[2])) +tabs = json.load(open(sys.argv[3])) + +def find(o, k): + if isinstance(o, dict): + if k in o: return o[k] + for v in o.values(): + r = find(v, k) + if r is not None: return r + elif isinstance(o, list): + for v in o: + r = find(v, k) + if r is not None: return r + +def labels(i): return [l.strip() for l in (i.get("labels") or "").split() if l.strip()] if isinstance(i.get("labels"), str) else [l["name"] for l in i.get("labels") or []] + +open_idx = {i["index"] for i in open_issues} +by_idx = {i["index"]: i for i in open_issues} + +# Herdr 裡活著的 agent:名稱 issue-N,或掛在標籤 #N 的 tab 上 +running = set() +for a in find(agents, "agents") or []: + m = re.fullmatch(r"issue-(\d+)", str(a.get("name") or "")) + if m: running.add(m.group(1)) +agent_tabs = {a.get("tab_id") for a in find(agents, "agents") or []} +for t in find(tabs, "tabs") or []: + m = re.fullmatch(r"#(\d+)", str(t.get("label") or "")) + if m and t.get("tab_id") in agent_tabs: running.add(m.group(1)) + +def blockers(i): + m = re.search(r"Blocked by:\s*([^\n]*)", i.get("body") or "") + return re.findall(r"#(\d+)", m.group(1)) if m else [] + +def spec(i): + return next((l for l in labels(i) if l.startswith("規格/#") or l.startswith("總覽/#")), "") + +runnable, blocked, stale = [], [], [] +for i in sorted(open_issues, key=lambda x: int(x["index"])): + ls = labels(i) + if "ready-for-agent" not in ls: continue + if "類型/規格" in ls or "類型/總覽" in ls: continue + n = i["index"] + if n in running: continue + if "in-progress" in ls: + stale.append(n); continue + open_blockers = [b for b in blockers(i) if b in open_idx] + if open_blockers: + blocked.append((n, open_blockers)); continue + runnable.append((n, spec(i), i["title"])) + +print(f"可執行({len(runnable)}):編號\t規格\t標題") +for n, s, t in runnable: + print(f"#{n}\t{s}\t{t}") +print() +print("Herdr 執行中:" + (" ".join(f"#{n}" for n in sorted(running, key=int)) or "無")) +if stale: + print("掛 in-progress 但沒有 agent(需處理,重跑 start 會接續):" + " ".join(f"#{n}" for n in stale)) +if blocked: + print("被阻擋:" + " ".join(f"#{n}←{','.join('#'+b for b in bs)}" for n, bs in blocked)) diff --git a/skills/herdr-issue/scripts/ready.sh b/skills/herdr-issue/scripts/ready.sh new file mode 100755 index 0000000..a16ba2c --- /dev/null +++ b/skills/herdr-issue/scripts/ready.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# 用法:ready.sh +# 列出可執行的議題(open、ready-for-agent、blocker 全關、Herdr 沒在跑、沒掛 in-progress)。 +source "$(dirname "$0")/lib.sh" +REPO="${1:?用法:ready.sh }" +T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT +tea issues list --repo "$REPO" --state open --limit 500 --output json --fields index,title,labels,body > "$T/open.json" +herdr agent list > "$T/agents.json" +herdr tab list --workspace "${HERDR_WORKSPACE_ID:?}" > "$T/tabs.json" +python3 "$SCRIPTS/ready.py" "$T/open.json" "$T/agents.json" "$T/tabs.json" diff --git a/skills/herdr-issue/scripts/start.sh b/skills/herdr-issue/scripts/start.sh new file mode 100755 index 0000000..ff0cc95 --- /dev/null +++ b/skills/herdr-issue/scripts/start.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# 用法:start.sh +# 在目前 clone 旁建 worktree、開 Herdr tab、啟動 claude、送提示詞、掛 in-progress 標籤。 +# 最後一行印出 JSON:{"tab":..,"pane":..,"path":..,"resumed":..} +source "$(dirname "$0")/lib.sh" +REPO="${1:?}"; N="${2:?用法:start.sh }" +LINK="https://gitea.jsc.idv.tw/$REPO/issues/$N" +test "${HERDR_ENV:-}" = 1 || die "不在 Herdr 裡(HERDR_ENV≠1)" +CLONE="$(clone_root_for "$REPO")" +BRANCH="issue-$N" +WT="$(worktree_path "$N" "$CLONE")" + +if [ "$(agent_status "$N")" != missing ]; then + die "issue-$N 的 agent 還活著(tab $(tab_for_issue "$N")),不重複啟動" +fi +[ "$(issue_state "$REPO" "$N")" = open ] || die "#$N 不是 open" + +git -C "$CLONE" fetch --quiet origin +RESUMED=false +if git -C "$CLONE" show-ref --verify --quiet "refs/heads/$BRANCH"; then + RESUMED=true + if ! git -C "$CLONE" worktree list --porcelain | grep -qx "worktree $WT"; then + git -C "$CLONE" worktree add --quiet "$WT" "$BRANCH" + fi +else + git -C "$CLONE" worktree add --quiet -b "$BRANCH" "$WT" origin/main +fi + +TAB_JSON="$(herdr tab create --workspace "${HERDR_WORKSPACE_ID:?}" --cwd "$WT" --label "#$N" --no-focus)" +TAB="$(jq_py 'print(find(d,"tab")["tab_id"])' <<<"$TAB_JSON")" +PANE="$(jq_py 'print(find(d,"root_pane")["pane_id"])' <<<"$TAB_JSON")" + +for _ in 1 2 3 4 5 6; do + if herdr agent start "issue-$N" --kind claude --pane "$PANE" --timeout 90000 -- --model "$MODEL" >/dev/null 2>"$WT/.herdr-start.err"; then + rm -f "$WT/.herdr-start.err"; break + fi + sleep 2 +done +[ -f "$WT/.herdr-start.err" ] && { cat "$WT/.herdr-start.err" >&2; herdr tab close "$TAB" >/dev/null; die "agent 啟動失敗"; } + +if $RESUMED; then RESUME_LINE="分支已有先前進度,先看 git log 和議題核取方塊再接續。"; else RESUME_LINE=""; fi +PROMPT="$(sed -e "s|{LINK}|$LINK|g" -e "s|{REPO}|$REPO|g" -e "s|{N}|$N|g" -e "s|{S}|$SCRIPTS|g" -e "s|{RESUME}|$RESUME_LINE|" "$SCRIPTS/prompt.txt")" +herdr agent prompt "issue-$N" "$PROMPT" >/dev/null + +ensure_label "$REPO" +tea issues edit "$N" --repo "$REPO" --remove-labels "$LABEL" >/dev/null 2>&1 || true +tea issues edit "$N" --repo "$REPO" --add-labels "$LABEL" >/dev/null + +printf '{"tab":"%s","pane":"%s","path":"%s","resumed":%s}\n' "$TAB" "$PANE" "$WT" "$RESUMED" diff --git a/skills/herdr-issue/scripts/tick.sh b/skills/herdr-issue/scripts/tick.sh new file mode 100755 index 0000000..7be08cb --- /dev/null +++ b/skills/herdr-issue/scripts/tick.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# 用法:tick.sh <第幾項> +# 把議題內文第 k 個核取方塊(已勾的也算在內)改成「[x]」。每次都重新讀取最新內文再改。 +source "$(dirname "$0")/lib.sh" +REPO="${1:?}"; N="${2:?}"; K="${3:?用法:tick.sh <第幾項>}" +NEW="$(issue_json "$REPO" "$N" | python3 -c " +import sys,json,re +body=json.load(sys.stdin)['body'] +k=int(sys.argv[1]); i=[0] +def rep(m): + i[0]+=1 + return '[x]' if i[0]==k else m.group(0) +out=re.sub(r'\[[ xX]\]', rep, body) +if i[0]/dev/null +echo "#$N 第 $K 項已勾選,剩 $(grep -o '\[ \]' <<<"$NEW" | wc -l) 項" diff --git a/skills/herdr-issue/scripts/watch-issue.sh b/skills/herdr-issue/scripts/watch-issue.sh new file mode 100755 index 0000000..43e2cf6 --- /dev/null +++ b/skills/herdr-issue/scripts/watch-issue.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# 用法:watch-issue.sh (在 clone 裡以背景執行) +# 每 HERDR_ISSUE_WATCH_INTERVAL 秒(預設 5)查議題;agent blocked 超過 HERDR_ISSUE_BLOCKED_AFTER 秒(預設 60,只通知一次)或消失就彈 Herdr 通知;議題關閉時清理並往上檢查規格、總覽。 +source "$(dirname "$0")/lib.sh" +REPO="${1:?}"; N="${2:?用法:watch-issue.sh }" +CLONE="$(clone_root_for "$REPO")" +BRANCH="issue-$N" +WT="$(worktree_path "$N" "$CLONE")" +LAST="" +WATCH_INTERVAL="${HERDR_ISSUE_WATCH_INTERVAL:-5}" # 輪詢秒數(不耗額度,只查 Gitea 與 Herdr) +BLOCKED_AFTER="${HERDR_ISSUE_BLOCKED_AFTER:-60}" # blocked 連續超過幾秒才通知 +BLOCKED_SINCE=0 +NOTIFIED=false + +# 若