Merge pull request 'release: v0.1.9 develop 到 master' (#31) from develop into master

Reviewed-on: #31
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
This commit was merged in pull request #31.
This commit is contained in:
2026-08-27 04:15:00 +00:00
9 changed files with 269 additions and 34 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.1.8",
"version": "0.1.9",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills",
"author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.1.8",
"version": "0.1.9",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills"
}
+6 -6
View File
@@ -22,7 +22,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| 腳本 | 用途 |
| --- | --- |
| `tools/wp-gate.sh` | 工作包 PR 閘門,把「一個工作包的 PR 沒合併,擋的是相依於它的工作包,不是整份分析」從內文敘述變成程式判定,共三個用法。`check {owner}/{repo} {index} [--since {ISO 時間}]` 查一支 PR:已合併就呼叫 `jsc-hooks` 的 `sdlc-gate.sh wp-unlock` 解鎖並印 `status=merged`;沒合併就印 `status=open` 或 `status=closed-unmerged`(被關掉但沒合併不算完成),接著把 issue 留言、審查評語、行內留言全部逐行印出(`--since` 只印更新的,值取上一輪的 `latest=`),最後一行印 `latest={最新一筆留言的時間戳}` 供寫回分析頁。`check-deps {owner}/{repo} {wp-number}` 查某個候選工作包能不能挑:活抓分析頁 WBS 表的相依欄,逐一核對每個相依工作包的狀態與 PR 是否已合併,只認同一張分析頁上的 `WP-NN` 編號,指到別份計畫的文字項目查不了就列出來要求人工確認,不當成擋人的理由。`lock {owner}/{repo} {index}` 在 PR 開好後轉呼叫 `sdlc-gate.sh wp-lock` 記下未結清(一個工作包一支鎖檔,可以同時記好幾筆平行進行的工作包)。第一行固定 `status=...` 供程式判讀,其後為繁中說明;結束碼 `0`=已合併或無阻擋(含 `check-deps` 的 `ready`)、`1`=未合併或 `check-deps` 判定 `blocked`、`2`=用法錯誤、`3`=相依工具或 PR/分析頁查不到(**查不到就擋,不放行**)。狀態檔由 `jsc-hooks` 管(`$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,不綁 session,開新對話照樣擋),本檔只負責查詢與結清 |
| `tools/wp-gate.sh` | 工作包 PR 閘門,把「一個工作包的 PR 沒合併,擋的是相依於它的工作包,不是整份分析」從內文敘述變成程式判定,共三個用法。`check {owner}/{repo} {index} [--since {ISO 時間}]` 查一支 PR:已合併就呼叫 `jsc-hooks` 的 `sdlc-gate.sh wp-unlock` 解鎖並印 `status=merged`;沒合併就印 `status=open` 或 `status=closed-unmerged`(被關掉但沒合併不算完成),接著把 issue 留言、審查評語、行內留言全部逐行印出(`--since` 只印更新的,值取上一輪的 `latest=`),最後一行印 `latest={最新一筆留言的時間戳}` 供寫回分析頁。`check-deps {owner}/{repo} {wp-number}` 查某個候選工作包能不能挑:活抓分析頁 WBS 表的相依欄,逐一核對每個相依工作包的狀態與 PR 是否已合併,只認同一張分析頁上的 `WP-NN` 編號,指到別份計畫的文字項目查不了就列出來要求人工確認,不當成擋人的理由。`claim {owner}/{repo} {wp-number} [--analyze {分析頁頁名}]` 在領包當下轉呼叫 `sdlc-gate.sh wp-claim` 登錄歸屬。`lock {owner}/{repo} {index} [--wp {wp-number}]` 在 PR 開好後轉呼叫 `sdlc-gate.sh wp-lock` 記下未結清,`--wp` 把 PR 掛到該工作包名下。`owns {owner}/{repo} {index} [--wp {wp-number}]` 比對這支 PR 是不是自己這一包的:`owned` 放行、`foreign` 擋住並指出它掛在哪一包名下、查無歸屬(沒有領取紀錄、工作包還沒掛上 PR)一律 `unowned` 放行只提醒。第一行固定 `status=...` 供程式判讀,其後為繁中說明;結束碼 `0`=已合併或無阻擋(含 `check-deps` 的 `ready`、`claim` 的 `claimed`、`owns` 的 `owned` 與 `unowned`)、`1`=未合併、`check-deps` 判定 `blocked` 或 `owns` 判定 `foreign`、`2`=用法錯誤、`3`=相依工具或 PR/分析頁查不到(**查不到就擋,不放行**),或歸屬登錄不了。狀態檔由 `jsc-hooks` 產生(領取檔 `$JSC_HOME/wp/{owner}-{repo}.claim`、鎖檔 `$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,純文字 key=value 一行一欄),不綁 session,開新對話照樣擋;本檔只讀不寫,寫入一律走 `sdlc-gate.sh` 的子命令 |
| `tools/stage-report.sh` | 階段收尾回報,四個階段共用。`stage-report.sh {plan\|analyze\|implement\|maintain}` 加 `--page TYPE:PAGE`(本階段寫過的每一頁,可重複)產出繁中回報:模型閘門判定(轉述 `sdlc-gate.sh report`,不自評)、工作日誌連結(`--worklog`、`--worklog-heading` 組出導向條目標題的錨點)、所有寫入的 wiki 絕對網址。實作階段再加 `--worktree`、`--source-branch`、`--work-branch`、`--target-branch`、`--pr`,來源分支在不在遠端、工作分支幾個 commit、推送了沒,都由腳本現查。沒有工作日誌時警告使用者檢查,並用 `--pending-file`、`--log-hash` 把內容交給 `jsc-log/tools/worklog-pending.sh` 暫存,下次寫日誌一併寫入。結束碼 `0`=完整、`1`=有警告(**警告不是阻擋**)、`2`=用法錯誤、`3`=相依工具找不到 |
## Skills 目錄
@@ -41,11 +41,11 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `implement`
實作:先確認來源分支(同時是 PR 目標)→ 領包前先跑 `tools/wp-gate.sh check` 把每個已開 PR 但沒合併的工作包留言逐筆印出並修正(以 sub agent 回原 worktree、推同一條工作分支,不開第二個 PR),**這一步只結清舊 PR 的留言,不擋別的工作包**——修不動或純討論的留言忽略但要在回報裡列出,處理過的時間戳寫回分析頁的 PR 欄位當下一輪的 `--since` → 產生工作證鎖定「未完成、無工作證」的候選工作包(交付工作包優先),**每個候選都跑 `tools/wp-gate.sh check-deps` 判斷能不能挑**:活查它在分析頁上的相依工作包是否都已合併,只有相依於它的包才會被一支未合併的 PR 擋住,跟它無關的工作包可以平行進行 → 交付工作包開工前先確認交付內容(API 文件、由使用者輸入,見 `references/deliver-formats.md`)→ **動程式碼前先從 `origin/{source-branch}` 建立 worktree**(`.worktree/{analysis-HASH}/{wp-number}/{repo}`,一個工作包一個 worktree,分支處理依決策樹詢問)→ 在 worktree 內逐項 TDD 實作、每完成一項立即更新 wiki → 程式碼審查 → **每完成一個工作包就 commit、push、PR 回來源分支**(一包一 PR),接著 `tools/wp-gate.sh lock` 上鎖,合併後解鎖並移除 worktree → 詢問交付文件格式(`DELIVER_{HASH}` wiki 頁或 Gitea 議題留言)並產出 → 詢問是否加入維護目錄 → 階段回報(`tools/stage-report.sh`,多報工作目錄與來源、工作、目標三條分支)。寫程式碼時註解只寫「為什麼這樣寫」,工作包編號、分析頁編號、待辦編號、分支名、PR 編號一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`。
實作:先確認來源分支(同時是 PR 目標)→ 領包前先跑 `tools/wp-gate.sh check` 把每個已開 PR 但沒合併的工作包留言逐筆印出,動手前先跑 `tools/wp-gate.sh owns` 確認那支 PR 是自己這一包的,再依 `jsc-ask:ask` 決策樹與使用者對每一則留言達成共識才修(以 sub agent 回原 worktree、推同一條工作分支,不開第二個 PR),**這一步只結清舊 PR 的留言,不擋別的工作包**——修不動或純討論的留言忽略但要在回報裡列出,處理過的時間戳寫回分析頁**既有**的 PR 欄位當下一輪的 `--since` → 產生工作證鎖定「未完成、無工作證」的候選工作包(交付工作包優先),**每個候選都跑 `tools/wp-gate.sh check-deps` 判斷能不能挑**:活查它在分析頁上的相依工作包是否都已合併,只有相依於它的包才會被一支未合併的 PR 擋住,跟它無關的工作包可以平行進行 → 領到包立刻跑 `tools/wp-gate.sh claim` 記下歸屬 → 交付工作包開工前先確認交付內容(API 文件、由使用者輸入,見 `references/deliver-formats.md`)→ **動程式碼前先從 `origin/{source-branch}` 建立 worktree**(`.worktree/{analysis-HASH}/{wp-number}/{repo}`,一個工作包一個 worktree,分支處理依決策樹詢問)→ 在 worktree 內逐項 TDD 實作、每完成一項立即更新 wiki → 程式碼審查 → **每完成一個工作包就 commit、push、PR 回來源分支**(一包一 PR),接著 `tools/wp-gate.sh lock --wp` 上鎖並登記 PR 歸屬 → **用 `jsc-gitea/tools/pr-watch.sh` 盯到合併**(預設 60 秒輪詢、不自動退場;退出碼 `0` 已合併或關閉、`10` 有新留言就回頭跑同一套留言修正、`3` 查不到該 PR、`2` 參數或環境有問題),合併後解鎖並移除 worktree → 詢問交付文件格式(`DELIVER_{HASH}` wiki 頁或 Gitea 議題留言)並產出 → 詢問是否加入維護目錄 → 階段回報(`tools/stage-report.sh`,多報工作目錄與來源、工作、目標三條分支)。**每完成一個任務就寫一筆工作日誌**(`jsc-log:worklog`):一個工作包、一輪 PR 留言修正、一個獨立的修正提交各算一個任務,不等到階段結束才補一次;`stage-report.sh --pending-file` 暫存的內容併進同一次寫入,寫入成功才清除。寫程式碼時註解只寫「為什麼這樣寫」,工作包編號、分析頁編號、待辦編號、分支名、PR 編號一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`。
### `maintain`
維護:讀取維護期內的專案,每個專案一個 sub agent:fetch 後切 develop、master 並對齊 `origin/{branch}` → 提出至少五種維護方法,依決策樹讓使用者挑要做哪些 → commit / push / PR → 更新前次維護時間 → 階段回報(`tools/stage-report.sh`)。sub agent 改程式碼時註解只寫「為什麼這樣寫」,議題編號、commit hash、分支名、人名與 `@` 提及一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`。僅適用於維護期內已交付的專案;尚在實作中或未登記於 `MAINTAIN_CONTENTS` 的專案不適用。
維護:讀取維護期內的專案,每個專案一個 sub agent:fetch 後切 develop、master 並對齊 `origin/{branch}` → 提出至少五種維護方法,依決策樹讓使用者挑要做哪些 → commit / push / PR → **PR 開好立刻寫一筆工作日誌**(`jsc-log:worklog`,一個專案一筆,下一個專案開工前要先寫完)→ 更新前次維護時間 → 階段回報(`tools/stage-report.sh`)。sub agent 改程式碼時註解只寫「為什麼這樣寫」,議題編號、commit hash、分支名、人名與 `@` 提及一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`。僅適用於維護期內已交付的專案;尚在實作中或未登記於 `MAINTAIN_CONTENTS` 的專案不適用。
<!-- JSC-SKILLS:END -->
@@ -61,7 +61,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `references/stage-report.md` | 階段回報:四階段都要交的三項(模型能力標籤、工作日誌連結、所有寫入的 wiki 連結)、實作階段多交的四項、沒寫日誌時的暫存規則、提前停止也要回報 |
| `references/model-gate.md` | 模型閘門:執行順序、各階段必要標籤、阻擋與回報的鐵則 |
| `references/tdd.md` | 接縫、紅綠循環規則、反模式 |
| `references/branch.md` | 分支規則:**一律以遠端 `origin/{branch}` 為準、動作前先 fetch**、分析前確認來源分支、實作沿用同一條來源分支作為 PR 目標(一包一 PR,一個工作包的 PR 沒合併只擋相依於它的工作包,不擋整份分析,閘門分工見該檔)、實作一律在 `.worktree/{HASH}/{wp-number}/{repo}` 內進行——一個工作包一個 worktree,讓互不相依的工作包能平行進行不互相搶路徑(建立前問分支、PR 合併後才移除)、判定遠端預設分支、不破壞未提交變更 |
| `references/branch.md` | 分支規則:**一律以遠端 `origin/{branch}` 為準、動作前先 fetch**、分析前確認來源分支、實作沿用同一條來源分支作為 PR 目標(一包一 PR,一個工作包的 PR 沒合併只擋相依於它的工作包,不擋整份分析,閘門分工與工作包隔離的狀態檔格式見該檔)、分支階梯(表本身在 `jsc-meta` 的 `references/guidelines.md`「PR 分支階梯」,該檔只寫 sdlc 拿到 `jsc-git/tools/base-branch.sh --derive` 的回應之後要做什麼、推不出就中止問使用者)、實作一律在 `.worktree/{HASH}/{wp-number}/{repo}` 內進行——一個工作包一個 worktree,讓互不相依的工作包能平行進行不互相搶路徑(建立前問分支、PR 合併後才移除)、判定遠端預設分支、不破壞未提交變更 |
| `references/consensus.md` | 規劃與分析的提問規則:一輪不算問完、共識的兩個判定條件、未決項處理 |
| `references/deliver-formats.md` | 交付內容型別:API 文件必備欄位、範例資料優先序、既有端點的新舊參數標示 |
@@ -75,7 +75,7 @@ HASH 規則:`{owner}/{repo}` 的共用 wiki hash 一律由 `jsc-gitea/tools/ha
- [`jsc-cli`](https://gitea.jsc.idv.tw/plugins/cli):模型能力標籤與階段閘門判定(`tools/model-tags.sh`、`references/model-tags.md`、`jsc-cli:models`)、偏好模型鏈(`tools/model-config.sh`)
- [`jsc-hooks`](https://gitea.jsc.idv.tw/plugins/hooks):sdlc-gate 階段模型鎖定
- [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):wiki 讀寫
- [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):wiki 讀寫、實作階段等 PR 合併的輪詢(`tools/pr-watch.sh`)
- [`jsc-review`](https://gitea.jsc.idv.tw/plugins/review):實作完成後的程式碼審查
- [`jsc-git`](https://gitea.jsc.idv.tw/plugins/git) / [`jsc-pkg`](https://gitea.jsc.idv.tw/plugins/pkg):維護階段的 commit / PR 與套件更新
- [`jsc-log`](https://gitea.jsc.idv.tw/plugins/log):完工後的工作日誌
- [`jsc-log`](https://gitea.jsc.idv.tw/plugins/log):工作日誌,每完成一個任務就寫一筆
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.1.8",
"version": "0.1.9",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills/"
}
+27 -3
View File
@@ -60,6 +60,17 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
- 需要換分支才能讀到正確現況時,**停下來請使用者自己切換**。
- 不代為 `switch`、不 `stash`、不動工作區、不建分支。
## 分支階梯與 base 推導
階梯表本身(哪一種類型往上接哪一條、`{子功能}` 怎麼組、base 為什麼一律由 `jsc-git/tools/base-branch.sh --derive` 推導)的唯一來源,在 `jsc-meta` 的 [`references/guidelines.md`](https://gitea.jsc.idv.tw/plugins/meta/src/branch/develop/references/guidelines.md)「PR 分支階梯」那一節。本檔不抄第二份,只寫 sdlc 拿到腳本回應之後要做什麼:
| 腳本回應 | 這個階段要做的事 |
| --- | --- |
| 印出一條分支名(結束碼 `0`) | 拿它當 `jsc-git:pr` 的 base,不再自行改判 |
| 結束碼 `7`(推不出唯一合法基底) | **中止並問使用者**,不退回 `develop`——退回 `develop` 就是讓子功能越級直接進主線 |
| stderr 印出「已建立功能主幹」 | 在階段回報裡講明建了哪一條主幹,那是這次實作多長出來的一條分支 |
| 結束碼 `2`(分支名含非 ASCII) | 先把中文簡述翻成英文短語,過 `jsc-git/tools/slugify.sh {類型} {英文短語}` 再重組分支名 |
## 實作階段的分支選擇
實作在 worktree 內進行,工作分支從 `origin/{source-branch}` 長出來,做完再 PR 回同一條來源分支。
@@ -125,12 +136,25 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
| 層 | 誰執行 | 做什麼 |
| --- | --- | --- |
| 工具閘門 | `tools/wp-gate.sh` | 唯一去問 Gitea 的一方,也是唯一有權解鎖的一方。`check` 查合併狀態、印出全部留言、印 `latest=` 時間戳,只管那一支 PR 自己;`check-deps` 查某個候選工作包能不能挑,活抓分析頁的相依欄逐一核對;`lock` 在 PR 開好後記下未結清(一個工作包一支鎖檔)。結束碼 0 放行、1 擋住、2 用法錯誤、3 查不到(查不到就擋,不放行) |
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `plan`、`analyze`、`maintain`,放行 `implement`——擋住的是「跳去做別的階段」,不是「回來把這一包做完」。這一層是整個存取庫共用的粗粒度提醒,不是逐工作包判斷,細粒度的相依判斷交給 `check-deps` |
| 工具閘門 | `tools/wp-gate.sh` | 唯一去問 Gitea 的一方,也是唯一有權解鎖的一方。`check` 查合併狀態、印出全部留言、印 `latest=` 時間戳,只管那一支 PR 自己;`check-deps` 查某個候選工作包能不能挑,活抓分析頁的相依欄逐一核對;`claim` 在領包當下登錄歸屬;`lock` 在 PR 開好後記下未結清並把 PR 掛到工作包名下;`owns` 比對某支 PR 是不是自己這一包的。狀態檔的寫入一律轉呼叫 `sdlc-gate.sh`。結束碼 0 放行、1 擋住、2 用法錯誤、3 查不到(查不到就擋,不放行) |
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `plan`、`analyze`、`maintain`,放行 `implement`——擋住的是「跳去做別的階段」,不是「回來把這一包做完」。這一層是整個存取庫共用的粗粒度提醒,不是逐工作包判斷,細粒度的相依判斷交給 `check-deps`,歸屬比對交給 `owns` |
狀態檔不綁 session:PR 沒合併就是沒合併,開新對話照樣擋。
查驗程序的細節(何時帶 `--since`、留言逐筆的處理結果、修不動的留言怎麼辦、時間戳寫回哪裡)由 `skills/implement/SKILL.md` 步驟 4 擁有,本檔不重複。
### 工作包隔離:一個工作階段只碰自己領的那一包
同一份分析常有好幾包平行進行,每包各自的 worktree 與 PR。動任何一支 PR 之前先確認它是自己這一包的,靠的是狀態檔而不是記憶——記憶跨不了工作階段。
| 項目 | 規則 |
| --- | --- |
| 歸屬依據 | **分析頁上的工作包代號**。不看分支名、不看 worktree 目錄名,那兩個都可能被改,改了也沒有徵兆。`WP-03`、`WP-3`、`3` 視為同一包 |
| 狀態檔 | 領取檔 `$JSC_HOME/wp/{owner}-{repo}.claim`(欄位 `repo`、`wp`、`pr`、`analyze`、`claimed`,一個存取庫一支)與鎖檔 `$JSC_HOME/wp/{owner}-{repo}-{index}.pr`(欄位 `repo`、`index`、`wp`、`locked`)。兩者都是純文字 key=value,一行一欄 |
| 誰寫 | 只有 `jsc-hooks` 的 `sdlc-gate.sh`:領包時 `wp-claim`、開完 PR 時 `wp-lock` 的第四個參數帶工作包代號、結清或交回時 `wp-unclaim` 與 `wp-unlock`。`wp-gate.sh` 一律轉呼叫,**不自己拼檔案**——兩邊各拼各的,格式一改就對不上,而且誰寫壞了看不出來 |
| 誰讀 | `wp-gate.sh owns` 與 `sdlc-gate.sh wp-check`,兩邊都只讀不改 |
| 查無歸屬 | **放行只提醒**:沒有分析頁、查不到工作包代號、領取檔不存在、工作包還沒掛上 PR,一律 `status=unowned` 並 exit 0。這跟「查不到就擋」不衝突——那條講的是查得到卻查失敗,這條講的是根本還沒有歸屬可查,拿不存在的歸屬擋人會讓沒登錄過的工作包全部動不了 |
| 逃生門 | `JSC_WP_GATE=off`,由 hook 那一層認 |
查驗程序的細節(何時帶 `--since`、怎麼等 PR 合併、留言逐筆的處理結果、修不動的留言怎麼辦、時間戳寫回哪裡)由 `skills/implement/SKILL.md` 步驟 4 與步驟 11 擁有,本檔不重複。
## 不破壞既有工作
+4 -1
View File
@@ -30,7 +30,10 @@
## 沒寫工作日誌就暫存
階段跑完卻沒有工作日誌,內容只留在對話裡,換一個工作階段就沒了。所以:
每完成一個任務就要寫一筆工作日誌(一個工作包、一輪 PR 留言修正、一個獨立的修正提交各算一個),
所以做完事情的階段收尾時本來就有日誌可連。本節講的是**還沒完成任何任務就停下**的那種階段。
暫存不等於已寫入。內容只留在對話裡,換一個工作階段就沒了,所以:
1. 把這個階段要記的日誌內容寫成一個檔案。
2. 收尾時一起餵進去:`--pending-file {檔案} --log-hash {LOG_{HASH} 的 HASH}`。
+25 -12
View File
@@ -1,6 +1,6 @@
---
name: implement
description: SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), then confirm the analysis page's source branch - it is both the worktree base and the PR target. Every already-open work package's PR comments get triaged via tools/wp-gate.sh check (sub agents fix them in that package's own worktree), but that never blocks a different, unrelated package - independent work packages can proceed in parallel. Picking a candidate is gated per-candidate in code by tools/wp-gate.sh check-deps, which re-reads the analysis page's WBS dependency column and queries Gitea itself: only a candidate whose own declared dependencies are merged may be claimed. Claim a ready work package from ANALYZE_CONTENTS with a work ticket, confirm a delivery package's content type, then complete its TDD todos one at a time inside a worktree built from origin/{source-branch}, updating the wiki after every item, closing with jsc-review code-review, one PR back to the source branch, the chosen delivery document, an optional MAINTAIN_CONTENTS entry, and a tools/stage-report.sh report covering the model tag verdict, the worklog link, every wiki link written, the worktree and the three branches. Use when analysis is done and code must be written; not for planning or analysis.
description: SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), then confirm the analysis page's source branch - it is both the worktree base and the PR target. Every already-open work package's PR comments get triaged via tools/wp-gate.sh check and fixed by sub agents only after jsc-ask consensus, never blocking an unrelated package; picking a candidate is gated per-candidate in code by tools/wp-gate.sh check-deps, and claiming it records the package number via tools/wp-gate.sh claim so tools/wp-gate.sh owns keeps every session on its own package's PR. Claim a ready work package from ANALYZE_CONTENTS with a work ticket, confirm a delivery package's content type, then complete its TDD todos one at a time inside a worktree built from origin/{source-branch}, updating the wiki after every item, closing with jsc-review code-review, one PR back to the source branch, a jsc-gitea pr-watch.sh poll that holds until that PR merges, a jsc-log:worklog entry per finished task, the chosen delivery document, an optional MAINTAIN_CONTENTS entry, and a tools/stage-report.sh report covering the model tag verdict, the worklog link, every wiki link written, the worktree and the three branches. Use when analysis is done and code must be written; not for planning or analysis.
---
# implement
@@ -20,17 +20,22 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
4. **Settle every already-open work-package PR's comments — this keeps existing PRs moving, but does not by itself decide which new package may start (that's step 6)**:
1. Read the analysis page's PR column. For every work package holding a PR that is not marked merged, run `jsc-sdlc/tools/wp-gate.sh check {owner}/{repo} {index} --since {the comment timestamp recorded in that PR column}`. Drop `--since` when that package has no recorded timestamp yet.
2. Exit 0 (`status=merged`) clears that package: remove its worktree (`references/branch.md`) and mark the package done on the analysis page.
3. Exit 1 (`status=open` or `status=closed-unmerged`) means that package's own PR is not settled yet. Fix every comment the script printed — issue comments, review verdicts and inline comments alike. **Each round of fixes MUST run as a sub agent** inside that package's own worktree, pushing to the same work branch, so the PR updates itself. Never open a second PR for the same package, and never ask whether to fix or wait: the gate already decided. **This does not block picking a different, unrelated work package** — SDLC implementation can run several independent packages in parallel; an unmerged PR only holds back packages that depend on it (step 6 checks that specifically), never the whole analysis page.
4. Give every printed comment an outcome — fixed, no fix needed, or cannot fix. Ignore what you cannot fix, plus pure discussion and praise: do not reply on the PR and do not stop the flow, and list each ignored comment with its reason in your final report.
5. Write the script's `latest=` value into that work package's PR column, appended after the existing PR link as `#{index} 已處理留言 {ISO time}`, and save the page back to the wiki. Next run passes it as `--since`, so handled comments stay handled. Reuse the existing PR column; the analysis page's columns belong to `analyze`.
6. Exit 3 means the gate could not decide (a missing dependency, or the PR could not be found). Report it and stop — an undecidable gate never counts as merged.
7. Completion condition: every work package holding an unmerged PR has had this run's comments triaged (fixed, no fix needed, or cannot fix) and reported; a package left unmerged after this does not block step 5/6 for packages that do not depend on it.
3. Exit 1 (`status=open` or `status=closed-unmerged`) means that package's own PR is not settled yet. **This does not block picking a different, unrelated work package** — SDLC implementation can run several independent packages in parallel; an unmerged PR only holds back packages that depend on it (step 6 checks that specifically), never the whole analysis page. Sub-steps 4.4 to 4.9 are the one comment round this skill owns; step 11.5 runs the same sub-steps for the PR it just opened.
4. **The PR has to be yours before you touch it.** Run `jsc-sdlc/tools/wp-gate.sh owns {owner}/{repo} {index} --wp {the work package number that PR column belongs to}`. `status=owned` → carry on. `status=foreign` (exit 1) → leave that PR alone, name the work package the script says holds it, and let that package's own session handle it. `status=unowned` (exit 0) → carry on, and report that ownership could not be determined. Completion condition: the script has run for that PR and its verdict is reported.
5. **Agree on the fix before making it.** Run the `jsc-ask:ask` decision tree over the comments the script printed — issue comments, review verdicts and inline comments alike — one option set per comment, each option stating its impact scope (which files it touches, whether it changes the package's TDD todos). **Never fix a comment automatically**: a reviewer's wording often allows two different fixes, and picking one silently costs another review round. Completion condition: the user has said how each comment is handled before any file is edited.
6. **Each round of fixes MUST run as a sub agent** inside that package's own worktree, pushing to the same work branch, so the PR updates itself. Hand the sub agent the agreed decisions and let the comment text stay inside it — the main agent keeps only the decisions and the bookkeeping. Never open a second PR for the same package.
7. Give every printed comment an outcome — fixed, no fix needed, or cannot fix. Ignore what you cannot fix, plus pure discussion and praise: do not reply on the PR and do not stop the flow, and list each ignored comment with its reason in your final report.
8. Write the script's `latest=` value into that work package's PR column, appended after the existing PR link as `#{index} 已處理留言 {ISO time}`, and save the page back to the wiki. Next run passes it as `--since`, so handled comments stay handled. Reuse the existing PR column and add no new column; the analysis page's columns belong to `analyze`.
9. **One round of comment fixes is one finished task — call `jsc-log:worklog` now**, under the Rules section's one-task-one-entry rule. Completion condition: the entry for this round is saved on `LOG_{HASH}` before the next round starts.
10. Exit 3 means the gate could not decide (a missing dependency, or the PR could not be found). Report it and stop — an undecidable gate never counts as merged.
11. Completion condition: every work package holding an unmerged PR has had this run's comments triaged (fixed, no fix needed, or cannot fix), logged and reported; a package left unmerged after this does not block step 5/6 for packages that do not depend on it.
5. Read `ANALYZE_CONTENTS` via `jsc-gitea:wiki` and list what is unfinished: plan name, HASH, work package number, count of open items. A selectable work package must satisfy all three: **unfinished, not holding a work ticket, and — verified by step 6's `wp-gate.sh check-deps`, never by eyeballing the 相依 column yourself — dependency-free or all dependencies merged**. Completion condition: you have listed every selectable work package, or reported that none is selectable and stopped.
6. **Before picking, the candidate's own dependencies decide — the gate lives in code, not in this text, and it is scoped to that one candidate, never to every open PR on the page**:
1. For the candidate work package the user is about to choose, run `jsc-sdlc/tools/wp-gate.sh check-deps {owner}/{repo} {wp-number}`. It re-reads the analysis page and queries Gitea itself — it does not trust anything you already read or concluded.
2. Exit 0 (`status=ready`) → eligible; proceed to claim it. Exit 1 (`status=blocked`) → not eligible; report which dependency work package's PR is not merged yet, and offer the user the other eligible candidates instead of this one. Exit 3 (`status=missing-dep`) is an undecidable gate — report it and stop; never treat an undecidable result as either ready or blocked.
3. A dependency phrased as text pointing outside this analysis page (another plan, another analysis) cannot be checked by the script; it comes back listed separately as needing manual confirmation. Confirm it with the user before proceeding — an unchecked cross-page dependency is not the same as a cleared one.
4. Let the user pick per `jsc-ask:ask` rules from the eligible candidates only (options state open-item count and estimated effort). **List delivery packages (交付 `是`) first** — the analysis page makes `WP-01` the standalone delivery package, so keep that order in the options. Write the ticket into that work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki. Completion condition: `check-deps` returned `status=ready` for the picked candidate (any cross-page dependency confirmed with the user), the ticket is saved on the wiki; only then may you proceed.
4. Let the user pick per `jsc-ask:ask` rules from the eligible candidates only (options state open-item count and estimated effort). **List delivery packages (交付 `是`) first** — the analysis page makes `WP-01` the standalone delivery package, so keep that order in the options. Write the ticket into that work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki.
5. **Record the claim in code, so later steps can tell this package's PR from any other package's**: run `jsc-sdlc/tools/wp-gate.sh claim {owner}/{repo} {wp-number} --analyze ANALYZE_{HASH}`, with the number read off the analysis page — the work package number is the only thing ownership is judged by (`references/branch.md`). One repository holds one claim at a time; `wp-gate.sh check` hands it back once the PR merges. Completion condition: `check-deps` returned `status=ready` for the picked candidate (any cross-page dependency confirmed with the user), the ticket is saved on the wiki, and `claim` returned `status=claimed`; only then may you proceed.
7. **A delivery/handover package confirms its content before its first todo**:
1. Ask per `jsc-ask:ask` rules what this delivery must contain. The options are fixed: **1. API 文件** and **2. 由使用者輸入**. State the impact scope on each. **Never assume the type, and never skip this — the answer decides what the whole package produces.**
2. Required fields, sample-data order and the new-versus-existing parameter marking: `references/deliver-formats.md`.
@@ -42,11 +47,18 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
3. **A code comment states why the code is written this way; it never states where the work is documented.** The tracking numbers this stage always holds — the work package number, the analysis page number, the TDD todo number, the source branch name and the PR number — stay out of every code comment; write the reason itself into the comment instead. Full list and the allowed exceptions: `jsc-review/references/comment-scope.md`. `jsc-hooks/hooks/comment-scope.sh` compares each file after it is written and prints a warning; fix the flagged line at once, then carry on with the same item. Completion condition: the item's own diff holds no comment line carrying any of those numbers, and every warning the hook printed for this item is fixed.
4. Completion condition: every item of the package shows `[x]` on the saved analysis page, and each save happened before the next item's sub agent started.
10. When all items are done, sweep the work package's whole diff for the comment rule of step 9.3, then call `jsc-review:code-review` and wait for the verdict. Each round of fixes **MUST run as a sub agent** inside the same worktree. Completion condition: the review passes, and the diff handed to it holds no comment line carrying the work package number, the analysis page number, a TDD todo number, the source branch name or a PR number (full list: `jsc-review/references/comment-scope.md`).
11. **One work package finished → commit, push, PR back to the source branch. Then stop and wait for that PR**:
1. Call `jsc-git:pr` from inside the worktree, **passing `{source-branch}` as the base branch**. One package, one PR; the branch rules behind that are in `references/branch.md`.
11. **One work package finished → commit, push, PR back to the source branch, then hold on that PR until it merges**:
1. Call `jsc-git:pr` from inside the worktree, **passing `{source-branch}` as the base branch**. One package, one PR; the branch ladder and how the base is derived are in `references/branch.md`.
2. Write the PR URL and number into that work package's PR column on the analysis page and save it back to the wiki, so the next run of this skill can find it (step 4).
3. Run `jsc-sdlc/tools/wp-gate.sh lock {owner}/{repo} {index}`. The lock is what makes step 4's gate hold across work sessions — a new session starts blocked until that PR merges.
4. **Do not start another work package.** Completion condition: the PR exists, its URL is saved on the analysis page and reported to the user, the lock exists (`status=locked`), and the skill has stopped.
3. Run `jsc-sdlc/tools/wp-gate.sh lock {owner}/{repo} {index} --wp {wp-number}`. The lock is what makes step 4's gate hold across work sessions — a new session starts blocked until that PR merges — and `--wp` is what hangs this PR on the claim from step 6.5, so `owns` can keep other sessions off it. Leave `--wp` out and every session is free to touch this PR. Completion condition: the script printed `status=locked` and named the work package.
4. **The work package is finished the moment its PR is open — call `jsc-log:worklog` now**, under the Rules section's one-task-one-entry rule. Completion condition: the entry is saved on `LOG_{HASH}` before the wait starts.
5. **Wait for the merge with `jsc-gitea/tools/pr-watch.sh {owner}/{repo} {index}`** — it polls every 60 seconds (`JSC_PR_WATCH_INTERVAL` overrides), never times out, and never repeats a comment it already reported. Branch on its exit code:
- `0` — the PR merged or was closed. Run `jsc-sdlc/tools/wp-gate.sh check {owner}/{repo} {index}` to tell merged from closed-unmerged, release the lock and hand the claim back. `status=merged` → remove the worktree (`references/branch.md`) and mark the package done on the analysis page; `status=closed-unmerged` → report it and stop, closed without merging is not finished. Comments printed in that final round still get an outcome per step 4.5 to 4.9.
- `10` — new comments are waiting. Run step 4.4 to 4.9 over them (ownership, consensus, sub agent fixes, outcomes, timestamp, work log), then run `pr-watch.sh` again. Repeat for as many rounds as the reviewer needs.
- `3` — the PR could not be found. Report and stop; a PR nobody can find never counts as merged.
- `2` — bad arguments, or Gitea was unreachable on the very first poll. Fix the arguments or the environment and run it again; never fall back to eyeballing the PR page and calling it merged.
6. **Do not start another work package in this session.** An unrelated package can still proceed, but in its own session with its own worktree; packages that depend on this one stay blocked until step 4 or step 11.5 sees it merged.
7. Completion condition: the PR exists, its URL is saved on the analysis page and reported to the user, the lock existed (`status=locked`), the work log entry is saved, and `pr-watch.sh` has returned `0` with `wp-gate.sh check` confirming `status=merged` — or the run stopped on a reported `2`, `3` or `status=closed-unmerged`.
12. **Delivery document** — a finished work package is a delivery, so **always ask before producing it; never pick a format silently and never skip this step**:
1. Ask per `jsc-ask:ask` rules which format to produce. The options are fixed: **a `DELIVER_{HASH}` wiki page** or **a Gitea issue comment**. State the impact scope on each (the wiki page lives beside the plan and analysis pages; the issue comment reaches whoever follows that issue).
2. Both formats use the same structure — `templates/deliver-page.md`, in Traditional Chinese. Only the destination differs. Sample values and personal-data handling: `references/deliver-formats.md`.
@@ -56,13 +68,14 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
13. Ask per `jsc-ask:ask` rules whether to register this project for maintenance: append to `MAINTAIN_CONTENTS` with `templates/maintain-contents.md`. Required: repository `{owner}/{repo}`, maintenance method, start date. Optional: end date (NULL = maintain forever), last-maintained time. Completion condition: the user has answered, and a chosen registration is saved on the wiki.
14. **Stage report — the last thing this stage does, including every early stop** (the work package gate blocked, no work package was selectable, the source branch was missing from the remote). Run `tools/stage-report.sh implement` with:
- one `--page TYPE:{page}` per wiki page this run wrote — `ANALYZE_{HASH}`, `DELIVER_{HASH}` and `DELIVER_CONTENTS`, `MAINTAIN_CONTENTS`;
- `--worklog` and `--worklog-heading` when a work log entry exists; otherwise write this stage's log content to a file and pass `--pending-file {file} --log-hash {HASH}`, which holds it for the next `jsc-log:worklog` run;
- `--worklog` and `--worklog-heading` pointing at the entry step 11.4 wrote — following the Rules section's one-task-one-entry rule, a stage that finished anything already has one. `--pending-file {file} --log-hash {HASH}` is the fallback for a stage that stopped before any task finished: it holds the content for the next `jsc-log:worklog` run, and held content is not a written log;
- `--worktree {path} --source-branch {name} --work-branch {name} --pr {url}` — the script reads the commit count, the push state and whether the source branch exists on the remote by itself, so pass the names, not your own count.
Rules and exit codes: `references/stage-report.md`. Exit 1 is a warning, never a block. Completion condition: the script's output is reported to the user verbatim, and it names the worktree, all three branches and every wiki page this run wrote.
## Rules
- **One finished task, one work log entry.** A task is one of three things: one work package, one round of PR-comment fixes, or one standalone fix commit. Call `jsc-log:worklog` the moment one of them finishes — never let a stage end and then write a single catch-up entry, because by then the elapsed time, the token counts and the difficulties are gone. Every entry appends to the same `LOG_{HASH}` page, so one package that took five comment rounds leaves five entries. Content parked earlier by `tools/stage-report.sh --pending-file` is merged into that same write and cleared only once the write succeeds; parked content is not a written log.
- The work ticket is a mutex: always skip work packages that already hold a ticket; never take one over.
- Never batch wiki updates across items; one item, one update.
- Sample data written into code or fixtures follows the analysis page's 資料來源 column, under the rules in `references/deliver-formats.md`.
+4 -3
View File
@@ -1,6 +1,6 @@
---
name: maintain
description: SDLC maintenance stage. Gate on capability tags enforced in code by sdlc-gate (maintenance requires no specific tag, but the actual model id must be determinable from the transcript), read projects still inside their maintenance window from MAINTAIN_CONTENTS, then run one sub agent per project: switch to develop or master, propose at least five maintenance actions, commit to a new branch, push, and PR. Update the last-maintained timestamp afterward, then close with tools/stage-report.sh - model tag verdict, worklog link, every wiki link written. Use for periodic upkeep of delivered projects inside their maintenance window; not for projects still mid-implementation or not yet registered in MAINTAIN_CONTENTS.
description: SDLC maintenance stage. Gate on capability tags enforced in code by sdlc-gate (maintenance requires no specific tag, but the actual model id must be determinable from the transcript), read projects still inside their maintenance window from MAINTAIN_CONTENTS, then run one sub agent per project: switch to develop or master, propose at least five maintenance actions, commit to a new branch, push, and PR. Write a jsc-log:worklog entry per finished project and update the last-maintained timestamp afterward, then close with tools/stage-report.sh - model tag verdict, worklog link, every wiki link written. Use for periodic upkeep of delivered projects inside their maintenance window; not for projects still mid-implementation or not yet registered in MAINTAIN_CONTENTS.
---
# maintain
@@ -25,6 +25,7 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
Completion condition: the user has picked the methods to apply, and every picked method is either applied or reported with the reason it could not be.
3. **A code comment states why the code is written this way; it never states where the work is tracked.** Issue numbers, commit hashes, branch names, people's names and `@` mentions stay out of every code comment this project's maintenance touches — including the comments the cleanup method rewrites. Full list and the allowed exceptions: `jsc-review/references/comment-scope.md`. `jsc-hooks/hooks/comment-scope.sh` compares each file after it is written and prints a warning; fix the flagged line at once, then carry on. Completion condition: this project's diff holds no comment line carrying an issue number, a commit hash, a branch name, a person's name or an `@` mention, and every warning the hook printed is fixed.
4. Commit the changes to a new branch per `jsc-git:commit`, push, then open a PR per `jsc-git:pr` back to the branch of step 3.1, passing it explicitly as the base. Completion condition: the PR exists, and you have reported its URL and number.
5. Update the project's last-maintained field (the zh-TW column 「前次維護時間」) in `MAINTAIN_CONTENTS` to today. Completion condition: `MAINTAIN_CONTENTS` shows today's date in 「前次維護時間」 for that project, saved on the wiki.
5. **One project's maintenance is one finished task — call `jsc-log:worklog` right after its PR is open.** A task is one of three things: one work package, one round of PR-comment fixes, or one standalone fix commit; this stage produces the third kind, one per project. Never let the stage end and then write a single catch-up entry, and never let a second project start before the first one's entry is saved — by then the elapsed time, the token counts and the difficulties are gone. Every entry appends to the same `LOG_{HASH}` page. Content parked earlier by `tools/stage-report.sh --pending-file` is merged into that same write and cleared only once the write succeeds; parked content is not a written log. Completion condition: this project's entry is saved on `LOG_{HASH}` before the next project's sub agent starts.
6. Update the project's last-maintained field (the zh-TW column 「前次維護時間」) in `MAINTAIN_CONTENTS` to today. Completion condition: `MAINTAIN_CONTENTS` shows today's date in 「前次維護時間」 for that project, saved on the wiki.
4. The main agent reports the summary: maintenance methods applied per project, PR links, and failure reasons. The report and all generated wiki content, commits, and PR descriptions stay Traditional Chinese per the STE100 rule. Completion condition: the summary names every project read in step 2, each with its applied methods and either a PR link or the reason it was skipped.
5. **Stage report — the last thing this stage does, including when no project was in window.** Run `tools/stage-report.sh maintain` with one `--page MAINTAIN:{page}` per wiki page this run wrote (`MAINTAIN_CONTENTS` counts), plus `--worklog` and `--worklog-heading` when a work log entry exists. No work log yet: write this stage's log content to a file and pass `--pending-file {file} --log-hash {HASH}` so it is held for the next `jsc-log:worklog` run. Rules and exit codes: `references/stage-report.md`. Exit 1 is a warning, never a block. Completion condition: the script's output is reported to the user verbatim, and every wiki page this run wrote appears in it.
5. **Stage report — the last thing this stage does, including when no project was in window.** Run `tools/stage-report.sh maintain` with one `--page MAINTAIN:{page}` per wiki page this run wrote (`MAINTAIN_CONTENTS` counts), plus `--worklog` and `--worklog-heading` pointing at the entries step 3.5 wrote. `--pending-file {file} --log-hash {HASH}` is the fallback for a stage that stopped before any project finished: it holds the content for the next `jsc-log:worklog` run, and held content is not a written log. Rules and exit codes: `references/stage-report.md`. Exit 1 is a warning, never a block. Completion condition: the script's output is reported to the user verbatim, and every wiki page this run wrote appears in it.
+200 -6
View File
@@ -21,6 +21,23 @@
# PR 欄(PR 有給就活查 Gitea 是否已合併),不吃 implement 技能自己讀到的任何快取或判斷。
# 只認同一張分析頁上的 WP-NN 編號;相依欄裡指到別份計畫或別頁分析的文字項目查不了,
# 照樣放行但會在說明裡列出來,要求人工確認。
# wp-gate.sh claim {owner}/{repo} {wp-number} [--analyze {分析頁頁名}]
# 領工作包當下登錄歸屬,轉呼叫 sdlc-gate.sh wp-claim(領包時還沒有 PR,PR 欄留空)。
# wp-gate.sh owns {owner}/{repo} {index} [--wp {wp-number}]
# 這支 PR 是不是自己這一包的:拿領取紀錄與鎖檔比對,不是就擋。
#
# 工作包歸屬狀態檔由 jsc-hooks 的 sdlc-gate.sh 產生,本檔只讀不寫:
# 領取檔 $JSC_HOME/wp/{owner}-{repo}.claim 欄位 repo=、wp=、pr=、analyze=、claimed=
# 鎖檔 $JSC_HOME/wp/{owner}-{repo}-{index}.pr 欄位 repo=、index=、wp=、locked=
# 都是純文字 key=value,一行一欄。工作包代號 WP-03、WP-3、3 視為同一包,比對前先正規化。
# 寫入一律走子命令(wp-claim、wp-lock 的第四個參數、wp-unclaim、wp-unlock),本檔不自己拼檔案:
# 兩邊各拼各的,格式一改就對不上,而且誰寫壞了看不出來。
# 歸屬一律以分析頁的工作包代號為準,不以分支名、不以目錄名——那兩個都可能被改。
#
# 查無歸屬就放行:
# 沒有分析頁、沒有領取紀錄、工作包還沒掛上 PR,都印 status=unowned 並 exit 0,只多印一行提醒。
# 理由跟「查不到就擋」不衝突:那條講的是閘門查得到卻查失敗,這條講的是根本還沒有歸屬可查。
# 拿不存在的歸屬去擋人,會讓沒登錄過的工作包全部動不了。
#
# 輸出: 第一行固定為 `status=...`(供程式判讀),其後為人類可讀的繁中說明。
# status=merged 已合併,鎖已解除,相依於它的工作包現在可以挑了
@@ -29,13 +46,18 @@
# status=locked 已記下這筆未結清的 PR
# status=ready check-deps 專用:相依的工作包都已結清,可以挑
# status=blocked check-deps 專用:至少一個相依工作包還沒結清,不得挑
# status=claimed claim 專用:已記下這一包的歸屬
# status=owned owns 專用:這支 PR 屬於自己這一包,放行
# status=foreign owns 專用:這支 PR 屬於別的工作包,擋住
# status=unowned owns 專用:查無歸屬,放行只提醒
# status=usage 用法錯誤
# status=missing-dep 相依腳本找不到、PR 查不到,或分析頁/WBS 列查不到
# check 未合併時,最後一行固定為 `latest={最新一筆留言的時間戳}`(一筆留言都沒有就是 `latest=`),
# 供呼叫端寫回分析頁,下一輪拿它當 --since,已處理過的留言就不會再處理一遍。
#
# 結束碼: 0=已合併或無阻擋(含 check-deps 的 ready) 1=未合併,或 check-deps 判定 blocked
# 2=用法錯誤 3=相依工具或 PR/分析頁查不到
# 結束碼: 0=已合併或無阻擋(含 check-deps 的 ready、claim 的 claimed、owns 的 owned 與 unowned)
# 1=未合併,或 check-deps 判定 blocked,或 owns 判定 foreign
# 2=用法錯誤 3=相依工具或 PR/分析頁查不到,或歸屬登錄不了
#
# 陷阱:
# - 「state=closed 但 merged=false」最容易被當成完成:那是 PR 被關掉、程式碼沒進去,
@@ -53,6 +75,10 @@
# 若寫的是另一份計畫或另一張分析頁的敘述(例如「節點建置」這種跨頁文字依賴),這支腳本
# 沒有能力去查那邊的狀態,只能原樣列出來要求人工確認,不會拿它當擋人的理由——結構上就
# 查不到的東西當成擋人的理由,跟前一條「查不到就擋」矛盾,會讓那個工作包永遠挑不到。
# - 工作包代號補零與否兩種寫法都會出現(WP-8、WP-08),比對前一律先過 wp_norm 正規化,
# 不然同一包的兩種寫法會被當成兩包,歸屬比對永遠對不上。
# - 領取檔一個存取庫一支,不是一個工作包一支。所以合併後交回領取紀錄之前要先確認
# 領取檔登記的正是這一包:中途改領別包時直接交回,會把還在進行的那一包的歸屬清掉。
set -u
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
@@ -63,11 +89,67 @@ usage() {
用法:
wp-gate.sh check {owner}/{repo} {index} [--since {ISO 時間}] 查 PR:合併就解鎖放行,沒合併就印出留言並擋住
wp-gate.sh check-deps {owner}/{repo} {wp-number} 候選工作包能不能挑:核對它在分析頁上的相依工作包是否都已結清
wp-gate.sh lock {owner}/{repo} {index} 開完 PR 後上鎖,讓閘門跨工作階段有效
結束碼: 0=已合併或無阻擋(含 check-deps 的 ready) 1=未合併,或 check-deps 判定 blocked 2=用法錯誤 3=相依工具或 PR/分析頁查不到
wp-gate.sh claim {owner}/{repo} {wp-number} [--analyze {頁名}] 領包當下登錄歸屬
wp-gate.sh lock {owner}/{repo} {index} [--wp {wp-number}] 開完 PR 後上鎖並把 PR 掛到工作包名下,讓閘門跨工作階段有效
wp-gate.sh owns {owner}/{repo} {index} [--wp {wp-number}] 這支 PR 是不是自己這一包的:不是就擋,查無歸屬就放行
結束碼: 0=已合併或無阻擋(含 check-deps 的 ready、claim 的 claimed、owns 的 owned 與 unowned) 1=未合併、check-deps 判定 blocked,或 owns 判定 foreign 2=用法錯誤 3=相依工具或 PR/分析頁查不到,或歸屬登錄不了
EOF
}
# --- 工作包歸屬:狀態檔由 jsc-hooks 的 sdlc-gate.sh 產生,本檔只讀不寫(格式見檔頭)---
wp_dir() { printf '%s/wp' "${JSC_HOME:-$HOME/.jsc}"; }
# WP-3、wp-03、3 是同一包。比對前一律先正規化成 WP-03;不合格回 1,由呼叫端印用法。
wp_norm() { # $1=工作包代號
_v="${1:-}"
case "$_v" in
[Ww][Pp]-*) _v=${_v#[Ww][Pp]-} ;;
[Ww][Pp]*) _v=${_v#[Ww][Pp]} ;;
esac
case "$_v" in
''|*[!0-9]*) return 1 ;;
esac
_v=$(printf '%s' "$_v" | sed 's/^0*//'); [ -n "$_v" ] || _v=0
printf 'WP-%02d' "$_v"
}
claim_file() { # $1={owner}/{repo}
printf '%s/%s.claim' "$(wp_dir)" "$(printf '%s' "$1" | tr '/' '-')"
}
lock_file() { # $1={owner}/{repo} $2=PR 編號
printf '%s/%s-%s.pr' "$(wp_dir)" "$(printf '%s' "$1" | tr '/' '-')" "$2"
}
wp_field() { # $1=狀態檔 $2=欄位名 -> 印出值,檔案或欄位不存在就印空字串
[ -f "$1" ] || return 0
sed -n "s/^$2=//p" "$1" 2>/dev/null | head -n1
}
# 某支 PR 登記在哪一個工作包名下;查不到印空字串。擋人時要講得出對方是誰,
# 只說「不是你的」,使用者分不出是自己領錯包,還是狀態檔留了上一輪的殘骸。
wp_of_pr() { # $1={owner}/{repo} $2=PR 編號
_w=$(wp_field "$(lock_file "$1" "$2")" wp)
[ -n "$_w" ] || { printf ''; return 0; }
wp_norm "$_w" || printf '%s' "$_w"
}
# 領取檔登記的工作包目前掛著哪一支 PR:領取檔的 pr 欄優先,空的就掃鎖檔補。
pr_of_wp() { # $1={owner}/{repo} $2=正規化後的 WP-NN
_p=$(wp_field "$(claim_file "$1")" pr)
if [ -n "$_p" ]; then printf '%s' "$_p"; return 0; fi
for _f in "$(wp_dir)/$(printf '%s' "$1" | tr '/' '-')"-*.pr; do
[ -f "$_f" ] || continue
_w=$(wp_field "$_f" wp)
[ -n "$_w" ] || continue
_w=$(wp_norm "$_w") || continue
[ "$_w" = "$2" ] || continue
wp_field "$_f" index
return 0
done
printf ''
}
# 找相依腳本。兩種版面都要顧到,否則腳本只在其中一種版面下會動:
# 並排存取庫(開發用):{workspace}/sdlc 旁邊就是 {workspace}/gitea、{workspace}/hooks
# 已安裝 plugin:每個 plugin 各有版本目錄,取排序最後的一份(通常即最新版)
@@ -175,6 +257,18 @@ case "$sub" in
else
echo "[jsc][工作包閘門][WARN]:鎖解不掉($repo 第 $index 號)。PR 確實已合併,但狀態檔還在,hook 會一直提醒。請手動執行:sdlc-gate.sh wp-unlock $repo $index" >&2
fi
# 領取紀錄一併交回:這一包已經結清,留著會讓下一輪的 owns 拿舊 PR 編號比對。
# 只有領取檔登記的正是這一包才交回——同一個存取庫可能已經改領別的包了。
claimed_wp=$(wp_field "$(claim_file "$repo")" wp)
[ -n "$claimed_wp" ] && claimed_wp=$(wp_norm "$claimed_wp")
merged_wp=$(wp_of_pr "$repo" "$index")
if [ -n "$claimed_wp" ] && { [ -z "$merged_wp" ] || [ "$claimed_wp" = "$merged_wp" ]; }; then
if sh "$gate" wp-unclaim "$repo" >/dev/null 2>&1 </dev/null; then
echo "領取紀錄已交回($repo 的 $claimed_wp)。"
else
echo "[jsc][工作包閘門][WARN]:領取紀錄交不回($repo 的 $claimed_wp)。這一包確實結清了,但紀錄還在,下一輪的 owns 會拿舊 PR 比對。請手動執行:sdlc-gate.sh wp-unclaim $repo" >&2
fi
fi
exit 0
fi
@@ -308,15 +402,110 @@ case "$sub" in
[ -n "$unknown" ] && echo "有查不了的相依項目(非本頁工作包編號,需人工確認是否已完成):$unknown"
exit 0 ;;
claim)
repo="${1:-}"; wp_arg="${2:-}"
valid_repo "$repo" || { echo 'status=usage'; usage; exit 2; }
wp=$(wp_norm "$wp_arg") || { echo 'status=usage'; usage; exit 2; }
shift 2
analyze=''
while [ "$#" -gt 0 ]; do
case "$1" in
--analyze) analyze="${2:-}"; [ -n "$analyze" ] || { echo 'status=usage'; usage; exit 2; }; shift 2 ;;
--analyze=*) analyze=${1#--analyze=}; [ -n "$analyze" ] || { echo 'status=usage'; usage; exit 2; }; shift ;;
*) echo 'status=usage'; usage; exit 2 ;;
esac
done
gate=$(sdlc_gate_sh) || missing_dep "找不到 jsc-hooks 的 hooks/sdlc-gate.sh,歸屬登錄不了。並排版面請確認 {workspace}/hooks 存在,已安裝版面請確認 jsc-hooks plugin 已安裝,或設定 JSC_HOOKS_DIR 指向它的 hooks 目錄。"
# 領包當下還沒有 PR,PR 編號位置給空字串佔位,分析頁頁名才排得到第四個參數。
# stdin 一定要關掉,理由同 check 分支的 wp-unlock 呼叫。
if ! out=$(sh "$gate" wp-claim "$repo" "$wp" '' "$analyze" 2>&1 </dev/null); then
echo 'status=missing-dep'
{
echo "[jsc][工作包閘門][ERR]:登錄不了 $repo 的 $wp。"
[ -n "$out" ] && printf '%s\n' "$out"
echo '沒登錄等於查無歸屬,之後每一支 PR 都會放行,隔離形同不存在。請修好之後重跑。'
} >&2
exit 3
fi
echo 'status=claimed'
echo "$repo 的 $wp 已登錄為本工作階段領取的工作包。開完 PR 再跑 wp-gate.sh lock $repo {index} --wp $wp 把 PR 掛上去。"
exit 0 ;;
owns)
repo="${1:-}"; index="${2:-}"
valid_repo "$repo" || { echo 'status=usage'; usage; exit 2; }
valid_index "$index" || { echo 'status=usage'; usage; exit 2; }
shift 2
wp=''
while [ "$#" -gt 0 ]; do
case "$1" in
--wp) wp=$(wp_norm "${2:-}") || { echo 'status=usage'; usage; exit 2; }; shift 2 ;;
--wp=*) wp=$(wp_norm "${1#--wp=}") || { echo 'status=usage'; usage; exit 2; }; shift ;;
*) echo 'status=usage'; usage; exit 2 ;;
esac
done
cf=$(claim_file "$repo")
claimed_wp=$(wp_field "$cf" wp)
[ -n "$claimed_wp" ] && claimed_wp=$(wp_norm "$claimed_wp")
if [ -z "$claimed_wp" ]; then
echo 'status=unowned'
echo "$repo 沒有領取紀錄($cf),判定不出第 $index 號 PR 的歸屬,本次放行。"
echo '領包時跑過 wp-gate.sh claim 才會有這筆紀錄,隔離也才會生效。'
exit 0
fi
# --wp 沒帶就用領取檔登記的工作包;帶了就要跟領取檔一致,不一致代表這個工作階段
# 根本沒領那一包,照樣不得處理它的 PR。
if [ -n "$wp" ] && [ "$wp" != "$claimed_wp" ]; then
echo 'status=foreign'
echo "$repo 目前領的是 $claimed_wp,不是 $wp,不得處理 $wp 的第 $index 號 PR。"
echo "要換包請先跑 sdlc-gate.sh wp-unclaim $repo 交回,再重新領取。"
exit 1
fi
[ -n "$wp" ] || wp="$claimed_wp"
own_pr=$(pr_of_wp "$repo" "$wp")
if [ -z "$own_pr" ]; then
echo 'status=unowned'
echo "$wp 還沒掛上任何 PR,比對不了第 $index 號 PR 的歸屬,本次放行。"
echo "開完 PR 跑 wp-gate.sh lock $repo {index} --wp $wp 之後才比對得了。"
exit 0
fi
if [ "$own_pr" = "$index" ]; then
echo 'status=owned'
echo "$repo 第 $index 號 PR 就是 $wp 這一包的,放行。"
exit 0
fi
other=$(wp_of_pr "$repo" "$index")
echo 'status=foreign'
echo "$repo 第 $index 號 PR 不屬於 $wp($wp 掛的是第 $own_pr 號),不得處理。"
if [ -n "$other" ]; then
echo "那支 PR 掛在 $other 名下,請由領該包的工作階段處理。"
else
echo '狀態檔裡沒有哪一包掛著這支 PR,請先確認分析頁的 PR 欄與工作包編號對不對得上。'
fi
exit 1 ;;
lock)
repo="${1:-}"; index="${2:-}"
valid_repo "$repo" || { echo 'status=usage'; usage; exit 2; }
valid_index "$index" || { echo 'status=usage'; usage; exit 2; }
[ "$#" -le 2 ] || { echo 'status=usage'; usage; exit 2; }
shift 2
wp=''
while [ "$#" -gt 0 ]; do
case "$1" in
--wp) wp=$(wp_norm "${2:-}") || { echo 'status=usage'; usage; exit 2; }; shift 2 ;;
--wp=*) wp=$(wp_norm "${1#--wp=}") || { echo 'status=usage'; usage; exit 2; }; shift ;;
*) echo 'status=usage'; usage; exit 2 ;;
esac
done
gate=$(sdlc_gate_sh) || missing_dep "找不到 jsc-hooks 的 hooks/sdlc-gate.sh,鎖上不了。並排版面請確認 {workspace}/hooks 存在,已安裝版面請確認 jsc-hooks plugin 已安裝,或設定 JSC_HOOKS_DIR 指向它的 hooks 目錄。"
# stdin 一定要關掉,理由同 check 分支的 wp-unlock 呼叫。
if ! out=$(sh "$gate" wp-lock "$repo" "$index" 2>&1 </dev/null); then
# 工作包編號當第四個參數往下傳:hook 認得就把它寫進同一支狀態檔,不認得就原樣忽略,
# 兩種版本都不會壞。不傳的話 hook 只好另開一支以 PR 編號命名的檔,同一包生出兩份狀態。
if ! out=$(sh "$gate" wp-lock "$repo" "$index" "$wp" 2>&1 </dev/null); then
echo 'status=missing-dep'
{
echo "[jsc][工作包閘門][ERR]:鎖上不了($repo 第 $index 號)。"
@@ -327,6 +516,11 @@ case "$sub" in
fi
echo 'status=locked'
echo "$repo 第 $index 號 PR 已記為未結清。合併之後跑 wp-gate.sh check 才會解鎖。"
if [ -n "$wp" ]; then
echo "歸屬已記下:$wp ← 第 $index 號 PR。"
else
echo '[jsc][工作包閘門][WARN]:沒帶 --wp,這支 PR 掛不到任何工作包名下,owns 對它一律放行。分析頁上查得到工作包代號時請補帶 --wp。' >&2
fi
exit 0 ;;
*)