fix/skill-check-compliance-and-flow #44

Merged
admin merged 5 commits from fix/skill-check-compliance-and-flow into develop 2026-08-31 03:33:43 +00:00
16 changed files with 180 additions and 83 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.2.5",
"version": "0.2.6",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills",
"author": {
@@ -20,8 +20,9 @@
"jsc-cli": ">=0.2.1",
"jsc-git": ">=0.0.9",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8",
"jsc-hooks": ">=0.3.2",
"jsc-log": ">=0.1.1",
"jsc-pkg": ">=0.0.7",
"jsc-review": ">=0.0.8"
}
}
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.2.5",
"version": "0.2.6",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills",
"jsc": {
@@ -9,8 +9,9 @@
"jsc-cli": ">=0.2.1",
"jsc-git": ">=0.0.9",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8",
"jsc-hooks": ">=0.3.2",
"jsc-log": ">=0.1.1",
"jsc-pkg": ">=0.0.7",
"jsc-review": ">=0.0.8"
}
}
+5 -5
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} --analyze {分析頁頁名}` 查某個候選工作包能不能挑:活抓呼叫端指定分析頁的 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/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} --analyze {分析頁頁名}` 查某個候選工作包能不能挑:活抓呼叫端指定分析頁的 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 目錄
@@ -33,19 +33,19 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `plan`
規劃:讀計畫目錄 → 補充或新建計畫 → 決策樹持續提問,補全目標、範圍、可行性到達成共識(判定規則見 `references/consensus.md`,一輪不算問完)→ 產生使用者故事 → 寫回 `PLAN_{HASH}` → 階段回報(`tools/stage-report.sh`)。純邏輯,禁止程式碼與修改檔案。
規劃:讀計畫目錄 → 補充或新建計畫 → 決策樹持續提問,補全目標、範圍、可行性到達成共識(判定規則見 `references/consensus.md`,一輪不算問完)→ 產生使用者故事 → 寫回 `PLAN_{HASH}` → 階段回報(`tools/stage-report.sh`)。純邏輯,禁止程式碼與修改檔案;claude 另有 `jsc-hooks/hooks/write-guard.sh` 的階段寫入閘門把關,其餘四支 CLI 沒有 `PreToolUse`,只靠內文約束。工作包閘門對 `plan` 只提醒不擋(`analyze` 與 `maintain` 仍擋),放棄的是「手上工作包沒結清就別開新計畫」這道在製品上限。
### `analyze`
分析:先確認來源分支 → 持續提問到達成共識(`references/consensus.md`)→ 搭配現況(工作目錄與 `REPO_{HASH}` 盤點複用)分析使用者故事 → WBS 產生編號工作包,`WP-01` 固定是獨立的交付、交接工作包,實作工作包相依於它 → CPM 估工時與天數 → TDD 拆待辦 → 寫回 `ANALYZE_{HASH}` → 階段回報(`tools/stage-report.sh`)。純邏輯,禁止程式碼與修改檔案。
分析:先併行讀 `PLAN_CONTENTS` 與 `ANALYZE_CONTENTS`,沒有計畫可選就立刻停(不先問分支,因為分支隨計畫而變)→ 選定要擴充的分析或要分析的計畫 → 確認來源分支並檢查工作目錄與 `origin/{來源分支}` 一致 → 持續提問到達成共識(`references/consensus.md`)→ 搭配現況(工作目錄與 `REPO_{HASH}` 盤點複用)分析使用者故事 → WBS 產生編號工作包,`WP-01` 固定是獨立的交付、交接工作包,實作工作包相依於它 → CPM 估工時與天數 → 先產生**使用者故事驗收計畫**(每個情境標明 `真實資料` 或 `邏輯推論`,並寫出輸入、預期結果、資料來源與對應工作包)→ 再拆 TDD 待辦 → 寫回 `ANALYZE_{HASH}` → 階段回報(`tools/stage-report.sh`)。純邏輯,禁止程式碼與修改檔案;claude 另有 `jsc-hooks/hooks/write-guard.sh` 的階段寫入閘門把關,其餘四支 CLI 沒有 `PreToolUse`,只靠內文約束。
### `implement`
實作:先確認來源分支(同時是 PR 目標)→ 領包前先跑 `tools/wp-gate.sh check` 把每個已開 PR 但沒合併的工作包留言逐筆印出,動手前先跑 `tools/wp-gate.sh owns` 確認那支 PR 是自己這一包的,再依 `jsc-ask:ask` 決策樹與使用者對每一則留言達成共識才修(以 sub agent 回原 worktree、推同一條工作分支,不開第二個 PR),**這一步只結清舊 PR 的留言,不擋別的工作包**——每則處理過的留言都要用 `jsc-gitea/tools/gitea.sh comment-reply` 回覆,純討論或讚美才可忽略並在回報中列出理由,處理過的時間戳寫回分析頁**既有**的 PR 欄位當下一輪的 `--since` → 產生工作證鎖定「未完成、無工作證」的候選工作包(交付工作包優先),**每個候選都跑 `tools/wp-gate.sh check-deps --analyze ANALYZE_{HASH}` 判斷能不能挑**:活查它在分析頁上的相依工作包是否都已合併,只有相依於它的包才會被一支未合併的 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 → **收尾稽核兩關並列,兩關都過才算工作包完成**:一關是 `jsc-review:code-review` 程式碼審查,另一關是 API 文件稽核——先跑 `jsc-review/tools/swagger-detect.sh` 判定專案支不支援 Swagger(退出碼 `0` 支援、`1` 不支援、`2` 參數或路徑有錯),支援才呼叫 `jsc-review:api-doc` 把控制器文件補到過關(沒過就以 sub agent 修完再稽核一次),不支援就**明確跳過並回報**,跳過算通過 → **每完成一個工作包就 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`,多報工作目錄與來源、工作、目標三條分支),PR 回報使用 `jsc-meta/references/pr-report.md` 的表格。**每完成一個任務就寫一筆工作日誌**(`jsc-log:worklog`):一個工作包、一輪 PR 留言修正、一個獨立的修正提交各算一個任務,不等到階段結束才補一次;`stage-report.sh --pending-file` 暫存的內容併進同一次寫入,寫入成功才清除。寫程式碼時註解只寫「為什麼這樣寫」,工作包編號、分析頁編號、待辦編號、分支名、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 的留言,不擋別的工作包**——每則處理過的留言都要用 `jsc-gitea/tools/gitea.sh comment-reply` 回覆,純討論或讚美才可忽略並在回報中列出理由,處理過的時間戳寫回分析頁**既有**的 PR 欄位當下一輪的 `--since` → 列出「未完成、無工作證」的候選工作包,沒有可挑的就停 → **提選項前先對每個候選各跑一次 `tools/wp-gate.sh check-deps --analyze ANALYZE_{HASH}`(候選之間可併行),只有 `ready` 排進選項**:活查它在分析頁上的相依工作包是否都已合併,只有相依於它的包才會被一支未合併的 PR 擋住,跟它無關的工作包可以平行進行(交付工作包排在選項最前)→ 領到包立刻跑 `tools/wp-gate.sh claim` 記下歸屬,**沒登錄成功就不得開工** → 確認來源分支(同時是 PR 目標):分析頁已記的值直接顯示並單鍵確認,只有「分析頁沒記錄」與「`origin/{來源分支}` 遠端不存在」兩種情況才走完整決策樹 → 產生工作證並寫回該工作包的「工作證」欄 → 交付工作包開工前先確認交付內容(API 文件、由使用者輸入,見 `references/deliver-formats.md`)→ **動程式碼前先從 `origin/{source-branch}` 建立 worktree**(`.worktree/{analysis-HASH}/{wp-number}/{repo}`,一個工作包一個 worktree,分支處理依決策樹詢問;多個存取庫的 worktree 併行建立)→ 在 worktree 內逐項 TDD 實作、每完成一項立即更新 wiki → **收尾稽核兩關同時啟動、並列進行,兩關都過才算工作包完成**:一關是 `jsc-review:code-review` 程式碼審查,另一關是 API 文件稽核——先跑 `jsc-review/tools/swagger-detect.sh` 判定專案支不支援 Swagger(退出碼 `0` 支援、`1` 不支援、`2` 參數或路徑有錯),支援才呼叫 `jsc-review:api-doc` 把控制器文件補到過關(沒過就以 sub agent 修完再稽核一次),不支援就**明確跳過並回報**,跳過算通過 → **每完成一個工作包就 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`,多報工作目錄與來源、工作、目標三條分支),PR 回報使用 `jsc-meta/references/pr-report.md` 的表格。**每完成一個任務就寫一筆工作日誌**(`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 → 用 `jsc-meta/references/pr-report.md` 的表格回報 PR → **PR 開好立刻寫一筆工作日誌**(`jsc-log:worklog`,一個專案一筆,下一個專案開工前要先寫完)→ 更新前次維護時間 → 階段回報(`tools/stage-report.sh`)。sub agent 改程式碼時註解只寫「為什麼這樣寫」,議題編號、commit hash、分支名、人名與 `@` 提及一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`。僅適用於維護期內已交付的專案;尚在實作中或未登記於 `MAINTAIN_CONTENTS` 的專案不適用。
維護:讀取維護期內的專案,**先整批併行 `git fetch` 並把每個專案切到 develop、master 並對齊 `origin/{branch}`**(專案之間互不相依),接著逐專案循序、每個專案一個 sub agent:提出至少五種維護方法,依決策樹讓使用者挑要做哪些 → commit / push / PR → 用 `jsc-meta/references/pr-report.md` 的表格回報 PR → **PR 開好立刻寫一筆工作日誌**(`jsc-log:worklog`,一個專案一筆,下一個專案開工前要先寫完)→ 更新前次維護時間 → 階段回報(`tools/stage-report.sh`)。sub agent 改程式碼時註解只寫「為什麼這樣寫」,議題編號、commit hash、分支名、人名與 `@` 提及一律不寫進註解,完整清單與白名單見 `jsc-review` 的 `references/comment-scope.md`;把關交給 `comment-scope.sh` 與提交前的 commit sweep,不再多做一輪人工全 diff 自查。**只有 claude 在寫檔當下就收到警告**;codex、copilot、antigravity 的 hook 要到回合或工作階段結束才掃,提交前的 commit sweep 是那三支唯一來得及的一道。僅適用於維護期內已交付的專案;尚在實作中或未登記於 `MAINTAIN_CONTENTS` 的專案不適用。
<!-- JSC-SKILLS:END -->
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-sdlc",
"version": "0.2.5",
"version": "0.2.6",
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)",
"skills": "./skills/",
"jsc": {
@@ -9,8 +9,9 @@
"jsc-cli": ">=0.2.1",
"jsc-git": ">=0.0.9",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8",
"jsc-hooks": ">=0.3.2",
"jsc-log": ">=0.1.1",
"jsc-pkg": ">=0.0.7",
"jsc-review": ">=0.0.8"
}
}
+2 -2
View File
@@ -137,7 +137,7 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
| 層 | 誰執行 | 做什麼 |
| --- | --- | --- |
| 工具閘門 | `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` |
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `analyze` 與 `maintain`,對 `plan` 只印提醒就放行,對 `implement` 一律放行——擋住的是「跳去做別的階段」,不是「回來把這一包做完」。`implement` 放行是閘門不自鎖的關鍵:結清那支 PR 的唯一路徑就是它,擋了就沒有人解得開。`plan` 降成提醒之後,放棄的是「手上工作包沒結清就別開新計畫」這道在製品上限,`analyze` 那道仍在,上限只是晚一個階段才生效。這一層是整個存取庫共用的粗粒度提醒,不是逐工作包判斷,細粒度的相依判斷交給 `check-deps`,歸屬比對交給 `owns` |
狀態檔不綁 session:PR 沒合併就是沒合併,開新對話照樣擋。
@@ -154,7 +154,7 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
| 查無歸屬 | **放行只提醒**:沒有分析頁、查不到工作包代號、領取檔不存在、工作包還沒掛上 PR,一律 `status=unowned` 並 exit 0。這跟「查不到就擋」不衝突——那條講的是查得到卻查失敗,這條講的是根本還沒有歸屬可查,拿不存在的歸屬擋人會讓沒登錄過的工作包全部動不了 |
| 逃生門 | `JSC_WP_GATE=off`,由 hook 那一層認 |
查驗程序的細節(何時帶 `--since`、怎麼等 PR 合併、留言逐筆的處理結果、修不動的留言怎麼辦、時間戳寫回哪裡)由 `skills/implement/SKILL.md` 步驟 4 與步驟 11 擁有,本檔不重複。
查驗程序的細節(何時帶 `--since`、怎麼等 PR 合併、留言逐筆的處理結果、修不動的留言怎麼辦、時間戳寫回哪裡)由 `skills/implement/SKILL.md` 步驟 2 與步驟 11 擁有,本檔不重複。
## 不破壞既有工作
+1 -1
View File
@@ -21,7 +21,7 @@ SDLC 每個階段動工前先過模型閘門。判定全在程式層,由 `jsc-
| 項目 | 規則 |
| --- | --- |
| 標籤來源 | 只認腳本的判定。不得宣稱自己沒驗證過的標籤,也不得用自己的判斷取代腳本結論 |
| 非零退出 | 一律視為阻擋:原文轉述腳本訊息、停止該技能、該回合不做別的事 |
| 非零退出 | 一律視為阻擋:原文轉述腳本訊息、停止該技能,該回合除了跑 `tools/stage-report.sh` 收尾回報以外不做別的事(提前停下來也要回報,見 `references/stage-report.md`) |
| `unlock` | 不得用來繞過閘門。要不要解鎖是使用者的決定 |
| 回報 | **每次都要回報**:階段、必要標籤、腳本從 transcript 讀到的實際模型 id、判定結果。通過與阻擋都要講——安靜通過看起來跟跳過檢查一樣,而判定搬進程式層的理由就是「宣稱有檢查」不可信 |
| 退出 0 | 該階段已上鎖。到下一階段的閘門重新上鎖之前,同一階段內把模型換成不合格的,下一輪提示會被 sdlc-gate hook 以 exit 2 擋下 |
+34 -13
View File
@@ -1,6 +1,6 @@
---
name: analyze
description: SDLC analysis stage. Gate on capability tags enforced in code by sdlc-gate (analyze requires reasoning-max), confirm the source branch, then pick a plan from PLAN_CONTENTS and question until consensus per references/consensus.md. Analyze its user stories against the current state (working directory plus REPO_{HASH} inventory for reuse), then run WBS with a standalone delivery/handover WP-01, CPM estimates and TDD todos. Write wiki page ANALYZE_{HASH} with real sample data, then close with tools/stage-report.sh - model tag verdict, worklog link, every wiki link written; logic only - never write code or modify files. Use after planning and before implementation; not for writing code (that is implement), and not before a plan exists in PLAN_CONTENTS.
description: SDLC analysis stage. Gate on capability tags enforced in code by sdlc-gate (analyze requires reasoning-max), pick a plan from PLAN_CONTENTS first, then confirm that plan's source branch and question until consensus per references/consensus.md. Analyze its user stories against the current state (working directory plus REPO_{HASH} inventory for reuse), then run WBS with a standalone delivery/handover WP-01, CPM estimates and TDD todos. Write wiki page ANALYZE_{HASH} with real sample data, then close with tools/stage-report.sh - model tag verdict, worklog link, every wiki link written; logic only - never write code or modify files. Use after planning and before implementation; not for writing code (that is implement), and not before a plan exists in PLAN_CONTENTS.
---
# analyze
@@ -9,33 +9,54 @@ Goal: create or extend the wiki analysis page `ANALYZE_{HASH}`.
This skill is a **logic-only** stage: never output code, and **never modify any file**.
`{HASH}` = the shared wiki hash for `{owner}/{repo}` used to build the `ANALYZE_{HASH}` page name, computed by `jsc-gitea/tools/hash-id` (see `jsc-gitea:wiki`).
All wiki reads and writes go through `jsc-gitea:wiki`.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Step 11 still runs after such a stop.
## Steps
1. **Model gate and stage lock** — run `jsc-cli/tools/model-tags.sh sync`, then `jsc-hooks/hooks/sdlc-gate.sh lock analyze`. This stage requires the `reasoning-max` capability tag. Rules: `references/model-gate.md`. Completion condition: the script exited 0, and you have reported the stage, the required tag, the actual model id it read from the transcript, and the verdict.
2. **Confirm the source branch** — the branch whose code counts as the current state:
2. **Find out whether there is anything to analyze, before anything else costs the user a round.** Read `PLAN_CONTENTS` for plans whose status is the literal 「未分析」 (name and HASH), and read `ANALYZE_CONTENTS` for existing analyses. **The two pages are independent — read them concurrently, and do not wait for the source branch: which branch the analysis reads from depends on the plan, so it is confirmed in step 4, after the target is known.** Completion condition: you have listed every 未分析 plan with its name and HASH plus every existing analysis, or reported that both lists are empty and stopped.
3. Let the user choose per `jsc-ask:ask` rules: **extend an existing analysis** or **analyze a new plan**. State the impact scope on every option. Completion condition: the user has picked one option explicitly, and you have named the target — the existing `ANALYZE_{HASH}` page, or the plan the new analysis covers.
4. **Confirm the source branch for that target** — the branch whose code counts as the current state, confirmed before any code is read and before the analysis page is written:
1. Run `git fetch --prune origin` first — without it, every `origin/...` reference is stale cache. Then report the working directory's current branch, the **remote** branches available (`git branch -r`; never `git branch`) and whether the working tree is clean.
2. Ask per `jsc-ask:ask` rules which branch the analysis reads from; state the impact scope on every option (analysing the wrong branch produces work packages for code that does not exist).
3. **The current state is always the remote branch `origin/{source-branch}`, never the local one.** The working directory's HEAD must point at the same commit as `origin/{source-branch}`; behind, ahead or diverged all mean you would be analysing code that is not what the remote holds. Report the gap (`git rev-list --left-right --count origin/{source-branch}...HEAD`) and stop — this stage never switches branches, never stashes, never pulls and never touches the working tree. Rules in `references/branch.md`.
4. Record the confirmed branch and the head sha **of `origin/{source-branch}`** on the analysis page. Completion condition: the user has confirmed the branch explicitly; never infer it from the current checkout alone.
3. Read `PLAN_CONTENTS` via `jsc-gitea:wiki` for plans whose status is the literal 「未分析」 (name and HASH), and read `ANALYZE_CONTENTS` for existing analyses. Completion condition: you have listed every 未分析 plan with its name and HASH plus every existing analysis, or reported that a list is empty.
4. Let the user choose per `jsc-ask:ask` rules: **extend an existing analysis** or **analyze a new plan**. State the impact scope on every option. Completion condition: the user has picked one option explicitly, and you have named the target — the existing `ANALYZE_{HASH}` page, or the plan the new analysis covers.
4. Record the confirmed branch and the head sha **of `origin/{source-branch}`** on the analysis page. Completion condition: the user has confirmed the branch explicitly, and the `origin` consistency check above has passed; never infer the branch from the current checkout alone.
5. Analyze the plan page's user stories one by one against the **current state**, **questioning until consensus** per `references/consensus.md` (the single authority for both planning and analysis): every answer produces the next question, and consensus needs both no output-changing unknown **and** the user's explicit confirmation. Never assume a missing detail, and never start the WBS while any item is still open. Current state means:
1. Every file in the working directory, at the commit `origin/{source-branch}` points to (verified in step 2).
1. Every file in the working directory, at the commit `origin/{source-branch}` points to (verified in step 4).
2. **Reuse an existing method or endpoint unless its logic cannot satisfy the requirement**:
- Check the `REPO_{HASH}` inventory page first. Re-inventory when the feature or endpoint is missing, or when the recorded commit sha differs from the current one.
- Re-inventory **MUST run as a sub agent**: analyze the repository's features and endpoints, attach the current commit sha, write back to `REPO_{HASH}` with `templates/repo-page.md`, and update `REPO_CONTENTS` per `templates/repo-contents.md`.
- Re-inventory **MUST run as a sub agent**: analyze the repository's features and endpoints, attach the current commit sha, write back to `REPO_{HASH}` with `templates/repo-page.md`, and upsert this repository's row in `REPO_CONTENTS` per `templates/repo-contents.md` — add the row if missing, otherwise refresh its commit sha and 盤點時間. `REPO_CONTENTS` is a contents page: read it back, change only this repository's row, and write the whole page. Never overwrite it wholesale, and never touch a row belonging to another repository.
- The `REPO_CONTENTS` read branches by exit code, and only exit 4 opens the create path — see "Contents pages are appended, never overwritten" below. `REPO_{HASH}` is a content page for one repository, so rewriting it whole is correct; the directory page around it is not.
- For each reuse candidate, confirm the file path and method name first, then analyze whether its logic fits the requirement. Reject a candidate only for a stated reason, and record both the candidate and that reason in the analysis page's 複用決策 field.
Completion condition: every user story has reached consensus under both conditions of `references/consensus.md`, and every reuse decision — reused, or rejected with its reason — is recorded in 複用決策.
6. Run a **Work Breakdown Structure (WBS)**: split the user stories into work packages, number them sequentially (`WP-01`, `WP-02`, ...) and mark dependencies. Completion condition: every user story on the plan page maps to at least one numbered work package, and every dependency edge is recorded in the WBS table's 相依 column.
7. **`WP-01` is always the delivery/handover work package** — see "Delivery package is WP-01" below. It stands alone, never merged into an implementation package, and every implementation package that consumes its spec depends on it. Completion condition: `WP-01` is marked 交付 `是` and holds only spec-shaped items, and every implementation package that consumes its spec names `WP-01` in its 相依 column — or the no-handover case below is confirmed with the user and its reason is written on the page.
8. Estimate every work package's effort in hours and days with the **Critical Path Method (CPM)**, and mark the critical path. Draw the critical path as a mermaid gantt chart per `references/cpm-chart.md` — its date/duration rules are mandatory, not a suggestion; skipping them is how the `Invalid date` rendering failure happens. Completion condition: every work package carries an hours figure and a days figure, the critical path plus its total days are written on the page, and the gantt chart follows `references/cpm-chart.md` exactly (integer-hour durations, `after` chaining, no `dateFormat X`).
9. 在 TDD 待辦前先產生 **使用者故事驗收計畫**,再把每個工作包拆進 **測試計畫(TDD)** 區塊,格式用 `[ ]`(未完成)與 `[x]`(完成)。每個使用者故事都要有驗收情境,並標明驗收方式是 `真實資料` 或 `邏輯推論`。簡單故事至少 1 個情境;中等故事至少 2 個情境,含 1 個主要流程與 1 個邊界或錯誤流程;複雜故事至少 3 個情境,含主要流程、邊界流程與失敗流程。每個情境都要寫出輸入、預期結果、資料來源與對應工作包。每個 TDD 待辦都是一個完整循環:一個接縫、一個先失敗的測試、一個最小實作、一次綠燈驗證,以及必要時的綠燈後重構。不要把紅燈測試、最小實作、綠燈驗證拆成不同待辦。接縫與反模式見 `references/tdd.md`。完成條件:每個使用者故事都在 `## 使用者故事驗收計畫` 有情境;每個情境都有明確驗收方式與資料來源;情境數量符合複雜度;每個工作包都在 `## 測試計畫(TDD)` 下有自己的小節;每個實作工作包至少有一個測試先行的 `[ ]` 待辦;每個待辦都寫出接縫、驗收情境、測試斷言行為、最小實作範圍與綠燈驗證方式。純交付工作包可以寫文件驗證或範例資料驗證待辦,但仍要寫出能證明規格可用的測試證據或審查證據。
10. Apply `templates/analyze-page.md` to create or update the analysis page and write it back via `jsc-gitea:wiki`. The page content is Traditional Chinese, exactly as the template dictates. Completion condition: the page is saved on the wiki and carries every section the template dictates, including the source branch, the head sha and the 未決項 section (「無」 when there is none).
11. If the analysis page is new: add it to `ANALYZE_CONTENTS` per `templates/analyze-contents.md`, and flip the plan's status in `PLAN_CONTENTS` to the literal 「已分析」. Completion condition: `ANALYZE_CONTENTS` shows the new row and `PLAN_CONTENTS` shows the literal 「已分析」, both saved on the wiki.
12. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, the working tree did not match `origin/{source-branch}`, no plan was selectable). Run `tools/stage-report.sh analyze` with one `--page TYPE:{page}` per wiki page this run wrote — `ANALYZE_{HASH}`, `ANALYZE_CONTENTS`, `PLAN_CONTENTS`, and `REPO_{HASH}` plus `REPO_CONTENTS` when a re-inventory happened — 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.
9. **Write the user story acceptance plan first, then the TDD todos.** The acceptance plan goes under the section `## 使用者故事驗收計畫`; every work package is then broken down under `## 測試計畫(TDD)`, with `[ ]` for an open item and `[x]` for a finished one.
1. Every user story gets acceptance scenarios, and every scenario states its acceptance method as either `真實資料` or `邏輯推論`. Scenario count follows the story's complexity: a simple story takes at least 1; a medium story at least 2, covering one main flow plus one boundary or error flow; a complex story at least 3, covering the main flow, the boundary flow and the failure flow.
2. Every scenario states its input, its expected result, its data source and the work package it belongs to.
3. **Every TDD todo is one whole cycle**: one seam, one failing test first, one minimal implementation, one green verification, and a post-green refactor where it is needed. Never split the red test, the minimal implementation and the green verification into separate todos. Seams and anti-patterns: `references/tdd.md`.
4. Completion condition: every user story has scenarios under `## 使用者故事驗收計畫`; every scenario carries an explicit acceptance method and data source; the scenario count matches the story's complexity; every work package has its own subsection under `## 測試計畫(TDD)`; every implementation work package holds at least one test-first `[ ]` todo; and every todo states its seam, its acceptance scenario, the behaviour the test asserts, the minimal implementation scope and how green is verified. A pure delivery package may use document-verification or sample-data-verification todos instead, and still states the test evidence or the review evidence that proves the spec is usable.
10. **Write the analysis page and its catalogue entries in one wiki pass.** Apply `templates/analyze-page.md` to create or update the analysis page and write it back via `jsc-gitea:wiki`; the page content is Traditional Chinese, exactly as the template dictates. `ANALYZE_CONTENTS` and `PLAN_CONTENTS` are then upserted per "Contents pages are appended, never overwritten" below: read each page back, add this analysis's row to `ANALYZE_CONTENTS` if it is missing and otherwise refresh it, flip only this plan's status in `PLAN_CONTENTS` to the literal 「已分析」, and write each whole page back. Completion condition: the analysis page is saved on the wiki carrying every section the template dictates — the source branch, the head sha and the 未決項 section (「無」 when there is none) included — and, for a new page, `ANALYZE_CONTENTS` shows its new row and `PLAN_CONTENTS` shows the literal 「已分析」, both saved on the wiki.
11. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, the working tree did not match `origin/{source-branch}`, no plan was selectable, a wiki read or write failed). Run `tools/stage-report.sh analyze` with one `--page TYPE:{page}` per wiki page this run wrote — `ANALYZE_{HASH}`, `ANALYZE_CONTENTS`, `PLAN_CONTENTS`, and `REPO_{HASH}` plus `REPO_CONTENTS` when a re-inventory happened — 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.
## Contents pages are appended, never overwritten
`ANALYZE_CONTENTS`, `PLAN_CONTENTS` and `REPO_CONTENTS` are shared directories: every row on them belongs to somebody's plan, analysis or repository, and this run reads none of those rows from anywhere else. So every write to them is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh it, then `wiki-put` the whole page. Whole-page overwrite is forbidden, and a row this run does not own stays untouched.
That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code:
| Exit | What this step does |
| --- | --- |
| 0 | the page is there — upsert this run's row into the content that came back, then write the whole page |
| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template |
| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing |
| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged |
Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes this step write a fresh template over a live directory, and every other row is gone — the write carries no merge and no backup. Content pages (`ANALYZE_{HASH}`, `PLAN_{HASH}`, `REPO_{HASH}`) are the opposite case: each belongs to one subject, so rewriting one whole is correct. The distinction is the page, not the write.
Completion condition: every contents-page write this stage made names the `wiki-get` exit code it branched on, and no page was created on any code other than 4.
## Delivery package is WP-01
@@ -54,5 +75,5 @@ Every sample value on the analysis page — request and response payloads, field
## Hard limits
- Never output a code snippet (file paths and method names are allowed).
- Never modify any file in the working directory.
- **Never modify any file in the working directory.** This limit is enforced in code where the CLI allows it: `jsc-hooks/hooks/write-guard.sh` in `stage` mode runs as a `PreToolUse` hook and blocks `Write`, `Edit` and `MultiEdit` while this stage's lock exists — the same lock state `jsc-hooks/hooks/sdlc-gate.sh lock analyze` writes in step 1. **Only claude has `PreToolUse`.** Codex, copilot, antigravity and kiro never reach that hook, so on those four CLIs this line is the only thing holding the limit.
- Never switch, create or clean branches in this stage; ask the user to do it.
+68 -44
View File
@@ -1,89 +1,113 @@
---
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 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 the two side-by-side audits jsc-review code-review and jsc-review api-doc (the latter gated by swagger-detect.sh and explicitly skipped where the project has no Swagger support), 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.
description: SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), settle every open work package's PR comments first, then claim a ready package before confirming 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; every candidate is gated in code by tools/wp-gate.sh check-deps before it reaches the options, and claiming 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 the two side-by-side audits jsc-review code-review and jsc-review api-doc (the latter gated by swagger-detect.sh and explicitly skipped where the project has no Swagger support), 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
Goal: complete the analysis page's todos one by one; **update the wiki status immediately after every completed item**.
All wiki reads and writes go through `jsc-gitea:wiki`.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Step 13 still runs after such a stop.
## Steps
1. **Model gate and stage lock** — run `jsc-cli/tools/model-tags.sh sync`, then `jsc-hooks/hooks/sdlc-gate.sh lock implement`. This stage requires the `coding` capability tag. Rules: `references/model-gate.md`. Completion condition: the script exited 0, and you have reported the stage, the required tag, the actual model id it read from the transcript, and the verdict.
2. **Confirm the source branch — it is also this stage's PR target**:
1. Run `git fetch --prune origin`, then read the source branch from the analysis page and report it, along with the current branch and whether the working tree is clean.
2. Ask per `jsc-ask:ask` rules to confirm that `origin/{source-branch}` is both the worktree's base and the PR target for every work package in this analysis. State the impact scope: each finished work package merges back into the source branch, and that branch as a whole reaches `develop` later as its own separate PR.
3. **A source branch missing from the remote is a stop-and-report condition, never a silent fallback.** That rule (section 「來源分支在遠端找不到」), the remote-only basis and the uncommitted-changes rules: `references/branch.md`.
4. Completion condition: the user has confirmed the source branch explicitly, and it is recorded on the analysis page next to the work ticket.
3. **Generate a work ticket**: format `TICKET_{yyyyMMdd}_{HHmmss}_{HASH}`. `{HASH}` = the shared wiki hash for `{owner}/{repo}`, computed by `jsc-gitea/tools/hash-id` (see `jsc-gitea:wiki`). Rename the current session to the ticket name; skip the rename only when the CLI exposes no rename command. Completion condition: the ticket string exists, and you have reported it together with which branch applied — renamed, or skipped because this CLI has no rename command.
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. **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. Reply to every handled comment with `jsc-gitea/tools/gitea.sh comment-reply {owner}/{repo} {index} {issue|review|inline} {comment id} {reply file}`. The reply states the outcome and the commit, file, or reason. Pure discussion and praise can be ignored only when you list the reason. Rules and the completion condition: `jsc-meta/references/pr-report.md`.
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} --analyze ANALYZE_{HASH}`. It re-reads the caller-specified analysis page and queries Gitea itself — it does not trust anything you already read or concluded, and it never guesses the page from the repository hash.
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.
2. **Read the analysis pages, then settle every already-open work-package PR's comments — this keeps existing PRs moving and can clear a dependency for step 4, but it does not by itself decide which new package may start (step 4 does)**:
1. Read `ANALYZE_CONTENTS`, then read every analysis page it lists as unfinished. **Keep this read: steps 3, 5 and 8 reuse it and never read the same pages again.** Completion condition: for every unfinished analysis page you hold its WBS table, its PR column, its source branch and the repositories it names.
2. **Prefetch every open PR's state in one batch.** 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. **These calls do not depend on each other — run them concurrently and collect every result before you ask the user anything.** The consensus rounds and the fixes that follow stay one comment at a time. Completion condition: every open PR has a recorded exit code and `status=` line.
3. Exit 0 (`status=merged`) clears that package: remove its worktree (`references/branch.md`) and mark the package done on the analysis page.
4. 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 4 checks that specifically), never the whole analysis page. Sub-steps 2.5 to 2.10 are the one comment round this skill owns; step 11.5 runs the same sub-steps for the PR it just opened.
5. **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` (exit 0) → 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. `status=usage` (exit 2) → bad arguments; fix them and run it again, and change nothing on that PR until the script returns a verdict. Completion condition: the script has run for that PR and its verdict is reported.
6. **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.
7. **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.
8. Give every printed comment an outcome — fixed, no fix needed, or cannot fix. Reply to every handled comment with `jsc-gitea/tools/gitea.sh comment-reply {owner}/{repo} {index} {issue|review|inline} {comment id} {reply file}`. The reply states the outcome and the commit, file, or reason. Pure discussion and praise can be ignored only when you list the reason. Rules and the completion condition: `jsc-meta/references/pr-report.md`.
9. 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`.
10. **One round of comment fixes is one finished task — call `jsc-log:worklog` now**, under the Rules section's one-task-one-entry rule. **Resolve that package's PLAN and ANALYZE wiki repositories and page URLs once and hand the resolved values to every call**: the same package's later rounds reuse them, and only a change of repository forces a fresh resolution. Completion condition: the entry for this round is saved on `LOG_{HASH}` before the next round starts.
11. Exit 2 (`status=usage`) means bad arguments — a malformed `{owner}/{repo}`, a missing index, or a `--since` value that is not the previous round's `latest=`. Fix the arguments and run it again. Completion condition: the rerun returned 0, 1 or 3; a usage error never counts as merged, settled or blocked.
12. Exit 3 (`status=missing-dep`) means the gate could not decide (a missing dependency script, or the PR could not be found). Report it and stop — an undecidable gate never counts as merged.
13. 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 steps 3 and 4 for packages that do not depend on it.
3. From the pages read in step 2.1, list what is unfinished: plan name, HASH, work package number, count of open items. A selectable work package satisfies both: **unfinished, and not holding a work ticket**. Dependencies are not judged here and never by eyeballing the 相依 column — step 4's `wp-gate.sh check-deps` is the only judge. Completion condition: you have listed every selectable work package, or reported that none is selectable and stopped.
4. **Every candidate passes the dependency gate before the user sees it — the gate lives in code, not in this text, and it judges one candidate at a time, never every open PR on the page**:
1. Run `jsc-sdlc/tools/wp-gate.sh check-deps {owner}/{repo} {wp-number} --analyze ANALYZE_{HASH}` **once per candidate from step 3, before asking the user anything**. Candidates do not depend on each other's verdict, so run these concurrently. The script re-reads the caller-specified analysis page and queries Gitea itself — it does not trust anything you already read or concluded, and it never guesses the page from the repository hash.
2. Branch on each candidate's exit code. Exit 0 (`status=ready`) → put it in the option list. Exit 1 (`status=blocked`) → keep it out of the option list, and report which dependency work package's PR is not merged yet. Exit 3 (`status=missing-dep`) is an undecidable gate — report it and stop; never treat an undecidable result as either ready or blocked. Exit 2 (`status=usage`) → bad arguments or a missing `--analyze`; fix them and run that candidate again, and leave it out of the options until the rerun returns a verdict. Completion condition: every candidate from step 3 carries one of these verdicts, and only the `ready` ones remain.
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.
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.
4. Let the user pick per `jsc-ask:ask` rules from the `ready` 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. Completion condition: the user has named one `ready` work package, and any cross-page dependency of it is confirmed.
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. Branch on the exit code: exit 0 (`status=claimed`) → proceed. Exit 2 (`status=usage`) → fix the arguments and run it again. Exit 3 (`status=missing-dep`) → the claim could not be recorded; report it and stop. **No recorded claim, no work**: without it the repository has no owner on record, `owns` answers `unowned` for every PR from then on, and every later session is waved through onto this package's PR. Completion condition: `claim` returned `status=claimed`; only then may you proceed.
5. **Confirm the source branch — it is also this stage's PR target**:
1. Run `git fetch --prune origin`, then take the source branch from the analysis page already read in step 2.1 and report it, along with the current branch and whether the working tree is clean.
2. **Show the recorded value and take a single-key confirmation.** The analysis page already carries the answer, so this is a confirmation, not a fresh question: print `origin/{source-branch}` as both the worktree base and the PR target for this work package, and accept one key to confirm it. Two cases have no shortcut and go through the full `jsc-ask:ask` decision tree, each option stating its impact scope: **the analysis page records no source branch**, and **`origin/{source-branch}` does not exist on the remote**.
3. **A source branch missing from the remote is a stop-and-report condition, never a silent fallback.** That rule (section 「來源分支在遠端找不到」), the remote-only basis and the uncommitted-changes rules: `references/branch.md`.
4. Completion condition: the user has confirmed the source branch — by the single key, or through the decision tree in either exception case — and it is recorded in the analysis page's 「來源分支」 column.
6. **Generate a work ticket and claim the package on the page**: format `TICKET_{yyyyMMdd}_{HHmmss}_{HASH}`. `{HASH}` = the shared wiki hash for `{owner}/{repo}`, computed by `jsc-gitea/tools/hash-id` (see `jsc-gitea:wiki`). Rename the current session to the ticket name; skip the rename only when the CLI exposes no rename command. Write the ticket into the picked work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki. Completion condition: the ticket string exists, it is saved in that package's 「工作證」 column on the wiki, and you have reported it together with which branch applied — renamed, or skipped because this CLI has no rename command.
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`.
3. Completion condition: the confirmed type is written into the analysis page's 交付型別 column and saved back to the wiki before the first todo starts.
8. **Create the worktree — before touching any code.** Run `git fetch --prune origin`, read the source branch and the repositories involved from the analysis page, then ask per `jsc-ask:ask` rules how to handle the branch. Path layout, the two `git worktree add` options, quoting, `.git/info/exclude` and the reporting duty: `references/branch.md`. Completion condition: every repository the analysis page names has a worktree built from `origin/{source-branch}`, and you have reported each worktree's path, checked-out branch, source branch and starting commit sha.
8. **Create the worktree — before touching any code.** Run `git fetch --prune origin` (this fetch is deliberate: the worktree must start from the newest remote state), then reuse the source branch confirmed in step 5 and the repository list read in step 2.1 — never read them off the page a second time. Ask per `jsc-ask:ask` rules how to handle the branch. **A multi-repository analysis builds its worktrees concurrently**; the repositories do not depend on each other. Path layout, the two `git worktree add` options, quoting, `.git/info/exclude` and the reporting duty: `references/branch.md`. Completion condition: every repository the analysis page names has a worktree built from `origin/{source-branch}`, and you have reported each worktree's path, checked-out branch, source branch and starting commit sha.
9. **Complete the work package's open items one at a time. Every item MUST run as a sub agent**, working inside the worktree:
1. One sub agent takes one item and follows the TDD loop: red before green, one vertical slice. Rules and anti-patterns: `references/tdd.md` (refactoring belongs to the review stage).
2. The main agent keeps only the wiki bookkeeping: when the sub agent reports the item done, flip its `[ ]` to `[x]` on the analysis page and save to the 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.
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` is wired on all five CLIs, but **it fires at a different moment on each, so never treat it as one uniform warning**: only claude scans the file the instant it is written, through `PostToolUse`; codex sweeps the whole worktree at the end of every turn, kiro at the next prompt submit, and copilot and antigravity only once when the session ends. Fix whatever it flags at once, then carry on with the same item. On the four CLIs that are not claude, none of that lands while the item is still being written, so **the one pass that still arrives in time is the `comment-scope.sh sweep` that `jsc-git:commit` runs before it commits at step 11.1** — that sweep is what keeps a flagged comment out of the commit, and a line it flags is fixed there, never waived. Completion condition: the item's own diff holds no comment line carrying any of those numbers, every warning the hook printed for this item is fixed, and on a CLI other than claude you have reported that the step 11.1 sweep is the pass being relied on.
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. **Two closing audits, side by side — a work package is finished only when both of them clear. Neither replaces the other**:
1. **Code review** — 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`).
10. **Two closing audits, side by side — a work package is finished only when both of them clear. Neither replaces the other, and neither waits for the other**:
1. **Code review** — when all items are done, call `jsc-review:code-review` over the work package's diff and wait for the verdict. The comment rule of step 9.3 is that review's own group, and `jsc-git:commit` sweeps the whole tree again at step 11.1, so this step runs no separate comment sweep of its own. Each round of fixes **MUST run as a sub agent** inside the same worktree. Completion condition: the review returned a passing verdict.
2. **API document audit — on a project that supports Swagger, the work package stays unfinished until its controller files are fully documented.** Whether the project supports Swagger is decided in code by `jsc-review/tools/swagger-detect.sh {worktree path}`; run it and branch on its exit code instead of judging the project yourself:
- `0` — the project supports Swagger. Call `jsc-review:api-doc` over the work package's changed controller files and wait for its verdict. What that audit checks belongs to `jsc-review:api-doc`; read the items there and keep no copy of them here.
- `1` — the project does not support Swagger. **Skip this audit explicitly and report the skip.** A reported skip is a pass, never a failure.
- `2` — bad arguments or a bad path. Fix them and run the script again; an undecidable detection is neither a pass nor a skip.
A failing verdict is fixed, never waived: each round of fixes **MUST run as a sub agent** inside the same worktree, and `jsc-review:api-doc` runs again over the fixed files until it passes.
Completion condition: `swagger-detect.sh` has run for this worktree and its exit code is reported, and either `jsc-review:api-doc` returned a passing verdict, or the skip is reported together with the exit code that caused it.
3. **Start both audits together and let them run side by side** — they read the same diff and neither one's verdict changes the other's input, so the closing wait costs one audit, not two. Completion condition: both audits have returned, and both cleared — a pass from `code-review`, and either a pass or a reported skip from the API document audit.
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} --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.
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 2).
3. Run `jsc-sdlc/tools/wp-gate.sh lock {owner}/{repo} {index} --wp {wp-number}`. The lock is what makes step 2'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 4.5, so `owns` can keep other sessions off it. Leave `--wp` out and every session is free to touch this PR. Branch on the exit code: exit 0 (`status=locked`) → proceed. Exit 2 (`status=usage`) → fix the arguments and run it again. Exit 3 (`status=missing-dep`) → the lock could not be recorded; report it and stop, because without the lock the next session starts unblocked on a PR that has not merged. 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, reusing the PLAN and ANALYZE wiki repositories and page URLs resolved for this package in step 2.10. 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.
- `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 2.6 to 2.10.
- `10` — new comments are waiting. Run step 2.5 to 2.10 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.
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 2 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 with the table format in `jsc-meta/references/pr-report.md`, 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).
12. **Deliver the document, then register for maintenance** — one closing pass over the two questions this stage owes the user. A finished work package is a delivery, so **always ask before producing it; never pick a format silently and never skip either question**:
1. Ask per `jsc-ask:ask` rules which delivery 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`.
3. Wiki page: write `DELIVER_{HASH}` through `jsc-gitea:wiki`, where `{HASH}` comes from `jsc-gitea/tools/hash-id` over `{owner}/{repo}` plus the work package number (for example `plugins/sdlc#WP-01`), so each work package gets its own page instead of overwriting the previous one. A new page is added to `DELIVER_CONTENTS` per `templates/deliver-contents.md`.
3. Wiki page: write `DELIVER_{HASH}` through `jsc-gitea:wiki`, where `{HASH}` comes from `jsc-gitea/tools/hash-id` over `{owner}/{repo}` plus the work package number (for example `plugins/sdlc#WP-01`), so each work package gets its own page instead of overwriting the previous one. Then upsert this work package's row in `DELIVER_CONTENTS` per `templates/deliver-contents.md` — add the row if missing, otherwise refresh it — following "Contents pages are appended, never overwritten" below.
4. Issue comment: confirm the issue number with the user (propose the one referenced by the work package or the PR; never guess), then post via `jsc-gitea/tools/gitea.sh api POST /repos/{owner}/{repo}/issues/{n}/comments` with the body passed in as a UTF-8 file — real newlines, never a literal `\n`.
5. Completion condition: the chosen format has actually been produced, and you have reported where it landed (wiki page name, or the comment URL).
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:
5. With the delivery produced, ask per `jsc-ask:ask` rules whether to register this project for maintenance: upsert this repository's row in `MAINTAIN_CONTENTS` with `templates/maintain-contents.md` — add the row if missing, otherwise refresh it — following "Contents pages are appended, never overwritten" below. Required: repository `{owner}/{repo}`, maintenance method, start date. Optional: end date (NULL = maintain forever), last-maintained time.
6. Completion condition: the chosen delivery format has actually been produced and you have reported where it landed (wiki page name, or the comment URL), **and** the user has answered the maintenance question with a chosen registration saved on the wiki.
13. **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, a wiki read or write failed). 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` 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.
## Contents pages are appended, never overwritten
`DELIVER_CONTENTS` and `MAINTAIN_CONTENTS` are shared directories: every row on them belongs to somebody's work package or repository, and this run reads none of those rows from anywhere else. So every write to them is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh it, then `wiki-put` the whole page. Whole-page overwrite is forbidden, and a row this run does not own stays untouched.
That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code:
| Exit | What this step does |
| --- | --- |
| 0 | the page is there — upsert this run's row into the content that came back, then write the whole page |
| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template |
| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing |
| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged |
Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes the step write a fresh template over a live directory, and every other work package's row is gone — the write carries no merge and no backup. `DELIVER_{HASH}` is the opposite case: it is a content page belonging to one work package, so writing it whole is correct. The distinction is the page, not the write.
Completion condition: every contents-page write this stage made names the `wiki-get` exit code it branched on, and no page was created on any code other than 4.
## 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.
- **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. The PLAN and ANALYZE wiki repositories and page URLs are resolved once per work package and reused by every entry of that package; only a change of repository forces a fresh resolution. 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. Between the claim (step 4.5) and the ticket write (step 6), `wp-gate.sh claim` is what holds the package — the claim is recorded in code, so a second session sees it even before the ticket reaches the page.
- 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`.
- Everything the skill writes out (wiki content, commit messages, PR descriptions) stays Traditional Chinese per the STE100 rule.
## What the single-key source-branch confirmation gives up
Step 5.2 used to run a full decision tree that made the user say out loud that the source branch is **also the PR target**, every run. That explicit two-way confirmation is gone: a user who taps the confirm key now agrees to the analysis page's recorded branch as the PR target without being asked about the target separately. A stale or wrong PR target on the analysis page therefore reaches the PR unchallenged. The two exception cases in step 5.2 — no recorded branch, and a branch missing from the remote — are what remains of that guard, and they keep the full decision tree.
+24 -7
View File
@@ -6,15 +6,15 @@ description: SDLC maintenance stage. Gate on capability tags enforced in code by
# maintain
Goal: run routine maintenance for every project in the maintenance contents page.
All wiki reads and writes go through `jsc-gitea:wiki`.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Step 5 still runs after such a stop.
## Steps
1. **Model gate and stage lock** — run `jsc-cli/tools/model-tags.sh sync`, then `jsc-hooks/hooks/sdlc-gate.sh lock maintain`. This stage requires no specific capability tag; the gate passes as long as the script can determine the actual model id. Rules: `references/model-gate.md`. Completion condition: the script exited 0, and you have reported the stage, the required tag, the actual model id it read from the transcript, and the verdict.
2. Read `MAINTAIN_CONTENTS` via `jsc-gitea:wiki` and filter projects **still inside their maintenance window**: start date ≤ today, and (end date is NULL or ≥ today). Completion condition: you have listed every in-window project with its `{owner}/{repo}` and window dates, or reported that none is in window and stopped.
3. Every project **MUST run as a sub agent** with this flow. Completion condition: every project listed in step 2 has its sub agent finished, and each one ends in either a PR link or a recorded skip reason.
1. Run `git fetch --prune origin`, then put the project on its maintenance branch and align it with `origin/{branch}`. Which branch that is, the remote-is-the-basis rule, the diverged case and the never-pull-never-reset rule all live in `references/branch.md`; never guess the branch name. Completion condition: the project's HEAD points at the same commit as `origin/{branch}`, or you have reported the gap and skipped this project.
2. Propose **at least five** maintenance methods, then let the user pick per `jsc-ask:ask` rules — every option states its impact scope (which files it touches, whether it can break the build, how much review it costs). Candidates:
3. **Align every project in one batch first, then run one sub agent per project.** Completion condition: every project listed in step 2 has its sub agent finished, and each one ends in either a PR link or a recorded skip reason.
1. **Batch prefetch, run by the main agent before any sub agent starts.** For every in-window project from step 2, run `git fetch --prune origin`, then put it on its maintenance branch and align it with `origin/{branch}`. **The projects are independent — run this batch concurrently**, and hand each sub agent the branch name and the aligned commit sha instead of letting it fetch again. Which branch that is, the remote-is-the-basis rule, the diverged case and the never-pull-never-reset rule all live in `references/branch.md`; never guess the branch name. From sub-step 3.2 onward the flow is one project at a time, sequential, so that 3.5's work log rule holds. Completion condition: every project's HEAD points at the same commit as `origin/{branch}`, or its gap is reported and that project is skipped and left out of the sub agent runs.
2. **From here on, one project at a time, and each project's maintenance MUST run as a sub agent.** Propose **at least five** maintenance methods, then let the user pick per `jsc-ask:ask` rules — every option states its impact scope (which files it touches, whether it can break the build, how much review it costs). Candidates:
- dependency updates (reuse `jsc-pkg:pkg-update`)
- security vulnerability scan and patching
- dead code and stale comment cleanup
@@ -23,9 +23,26 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
- build warning elimination
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.
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`. Two passes already cover the diff, so **run no separate manual sweep of your own**: `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 — and `jsc-git:commit` sweeps the whole working tree again in step 3.4, before anything is committed. **Coverage is not the same on every CLI**: only claude gets the per-file warning as the file is written. On codex, kiro, copilot and antigravity the hook fires late — at the end of the turn on codex, at the next prompt submit on kiro, at the end of the session on copilot and antigravity — so the pre-commit sweep in step 3.4 is the only pass on all four that lands in time to keep a flagged comment out of the commit. Completion condition: every warning the hook printed is fixed, and the step 3.4 sweep reported no remaining comment line carrying an issue number, a commit hash, a branch name, a person's name or an `@` mention.
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 it with the table format in `jsc-meta/references/pr-report.md`.
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.
6. Update the project's last-maintained field (the zh-TW column 「前次維護時間」) in `MAINTAIN_CONTENTS` to today, per "Contents pages are appended, never overwritten" below: read the page back, change only this project's row (add it if it is missing), and write the whole page. Completion condition: `MAINTAIN_CONTENTS` shows today's date in 「前次維護時間」 for that project, every other project's row is byte-for-byte unchanged, and the `wiki-get` exit code the write branched on is named.
4. The main agent reports the summary: maintenance methods applied per project, PR table rows, 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 table row 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` 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.
5. **Stage report — the last thing this stage does, including when no project was in window, and when a wiki read or write failed.** 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.
## Contents pages are appended, never overwritten
`MAINTAIN_CONTENTS` is a shared directory: every row on it belongs to somebody's project, and this run reads none of those rows from anywhere else. So step 3.6 is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh its 前次維護時間 — then `wiki-put` the whole page. Whole-page overwrite is forbidden, and a project this run did not maintain keeps its row untouched.
That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code:
| Exit | What this step does |
| --- | --- |
| 0 | the page is there — upsert this project's row into the content that came back, then write the whole page |
| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template, and it also means step 2 had no project to maintain |
| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing |
| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged |
Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes step 3.6 write a fresh template over a live directory, and every other project's maintenance window is gone — the write carries no merge and no backup. Content pages, which belong to one subject each, are the opposite case and may be rewritten whole. The distinction is the page, not the write.
Completion condition: the `MAINTAIN_CONTENTS` write names the `wiki-get` exit code it branched on, and no page was created on any code other than 4.
+25 -4
View File
@@ -9,7 +9,7 @@ Goal: create or extend the wiki plan page `PLAN_{HASH}`.
This skill is a **logic-only** stage: never output code, and **never modify any file**.
`{HASH}` = the shared wiki hash for `{owner}/{repo}` used to build the `PLAN_{HASH}` page name, computed by `jsc-gitea/tools/hash-id` (see `jsc-gitea:wiki`).
All wiki reads and writes go through `jsc-gitea:wiki`.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Step 8 still runs after such a stop.
## Steps
@@ -26,12 +26,33 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
Completion condition: all three items are settled under both conditions of `references/consensus.md` — no remaining unknown that would change the output, **and** the user's explicit confirmation of the summary you read back.
5. Turn the consensus into **user stories** (the zh-TW pattern 「身為⋯⋯我想要⋯⋯以便⋯⋯」), one per line. Completion condition: every consensus item is covered by at least one user story in that pattern, and no user story rests on an unanswered question.
6. Apply `templates/plan-page.md` to create or update the plan page, and write it back via `jsc-gitea:wiki`. The page content is Traditional Chinese, exactly as the template dictates. Completion condition: the page is saved on the wiki and carries every section the template dictates — goal, scope, feasibility, user stories and the consensus summary — with no placeholder left unfilled.
7. If the plan page is new, add it to `PLAN_CONTENTS` using the entry format of `templates/plan-contents.md`, with status set to the literal 「未分析」. Completion condition: `PLAN_CONTENTS` shows the plan's row with the literal 「未分析」, saved on the wiki.
8. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, no plan was selectable). Run `tools/stage-report.sh plan` with one `--page PLAN:{page}` per wiki page this run wrote (`PLAN_{HASH}` and `PLAN_CONTENTS` both count), 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.
7. Upsert this plan's row in `PLAN_CONTENTS` using the entry format of `templates/plan-contents.md`, with status set to the literal 「未分析」 — add the row if missing, otherwise refresh it. Read the page back first and write the whole page, per "Contents pages are appended, never overwritten" below; never overwrite it wholesale, and never touch a row belonging to another plan. Completion condition: `PLAN_CONTENTS` shows this plan's row with the literal 「未分析」, every other row is byte-for-byte unchanged, and the `wiki-get` exit code the write branched on is named.
8. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, no plan was selectable, a wiki read or write failed). Run `tools/stage-report.sh plan` with one `--page PLAN:{page}` per wiki page this run wrote (`PLAN_{HASH}` and `PLAN_CONTENTS` both count), 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.
## Contents pages are appended, never overwritten
`PLAN_CONTENTS` is a shared directory: every row on it belongs to somebody's plan, and this run reads none of those rows from anywhere else. So the write is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh it, then `wiki-put` the whole page. Whole-page overwrite is forbidden.
That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code:
| Exit | What this step does |
| --- | --- |
| 0 | the page is there — upsert this plan's row into the content that came back, then write the whole page |
| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template |
| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing |
| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged |
Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes step 7 write a fresh template over a live directory, and every other plan's row is gone — the write carries no merge and no backup. `PLAN_{HASH}` is the opposite case: it is a content page belonging to this one plan, so step 6 rewriting it whole is correct. The distinction is the page, not the write.
Completion condition: the `PLAN_CONTENTS` write names the `wiki-get` exit code it branched on, and no page was created on any code other than 4.
## Hard limits
- Never output a code snippet.
- Never modify any file in the working directory.
- **Never modify any file in the working directory.** This limit is enforced in code where the CLI allows it: `jsc-hooks/hooks/write-guard.sh` in `stage` mode runs as a `PreToolUse` hook and blocks `Write`, `Edit` and `MultiEdit` while this stage's lock exists — the same lock state `jsc-hooks/hooks/sdlc-gate.sh lock plan` writes in step 1. **Only claude has `PreToolUse`.** Codex, copilot, antigravity and kiro never reach that hook, so on those four CLIs this line is the only thing holding the limit.
- Never skip the decision tree and assume requirements.
- Never stop questioning after one round; consensus is reached only under `references/consensus.md`, and the user says so.
## What the work package gate no longer stops here
`jsc-hooks/hooks/sdlc-gate.sh wp-check` used to block this skill outright while the repository still held an unsettled work package PR. For `plan` it is now a reminder that prints and lets the run through. The protection given up is the **work-in-progress cap**: nothing stops a new plan from starting while packages from the last one are still open, so plans can pile up faster than they are implemented. The reminder still names the unsettled package, and the same gate still blocks `analyze` and `maintain`, so the cap holds one stage later. `implement` was always waved through — that is the path that settles the open PR, and a gate that blocked it would lock itself.
+2
View File
@@ -1,5 +1,7 @@
# 分析目錄
> 寫入語意:一列代表一份分析頁。寫入前先讀回整頁,該分析頁已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
| 計畫名稱 | 分析頁 | HASH | 工作包 | 未完成項目 | 狀態 |
| --- | --- | --- | --- | --- | --- |
| {計畫名稱} | [[{計畫名稱}|ANALYZE_{HASH}]] | {HASH} | WP-01、WP-02 | {n} | 未完成 |
+2
View File
@@ -1,5 +1,7 @@
# 交付目錄
> 寫入語意:一列代表一個工作包的交付。寫入前先讀回整頁,該工作包已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
<!-- 交付欄:是 = 交付、交接工作包(WP-01),接手者要據此動工;否 = 實作類工作包。 -->
| 計畫名稱 | 工作包 | 交付 | 交付型別 | 交付頁 | HASH | 存取庫 | 交付時間 |
+2
View File
@@ -1,5 +1,7 @@
# 維護目錄
> 寫入語意:一列代表一個受維護的存取庫。寫入前先讀回整頁,該存取庫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
<!-- 維護截止日 NULL = 永久維護 -->
| 存取庫 | 維護方式 | 維護起始日 | 維護截止日 | 前次維護時間 |
+2
View File
@@ -1,5 +1,7 @@
# 計畫目錄
> 寫入語意:一列代表一份計畫。寫入前先讀回整頁,該計畫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
| 計畫名稱 | 計畫頁 | 存取庫 | HASH | 狀態 | 建立時間 |
| --- | --- | --- | --- | --- | --- |
| {計畫名稱} | [[{計畫名稱}|PLAN_{HASH}]] | {owner}/{repo} | {HASH} | 未分析 | {yyyy-MM-dd} |
+2
View File
@@ -1,5 +1,7 @@
# 盤點目錄
> 寫入語意:一列代表一個存取庫的盤點。寫入前先讀回整頁,該存取庫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。
| 存取庫 | 盤點頁 | HASH | commit sha | 盤點時間 |
| --- | --- | --- | --- | --- |
| {owner}/{repo} | [[{owner}/{repo}|REPO_{HASH}]] | {HASH} | `{sha}` | {yyyy-MM-dd} |
+2 -1
View File
@@ -14,8 +14,9 @@
# wp-gate.sh check {owner}/{repo} {index} [--since {ISO 時間}]
# 查一支 PR。已合併就解鎖並放行;沒合併就把留言全部印出來並擋住。
# --since 只印比該時間更新的留言,值用上一輪印出的 latest=(UTC,形如 2026-08-25T10:19:59Z)。
# wp-gate.sh lock {owner}/{repo} {index}
# wp-gate.sh lock {owner}/{repo} {index} [--wp {wp-number}]
# PR 開好之後上鎖,讓閘門跨工作階段有效。轉呼叫 sdlc-gate.sh wp-lock。
# --wp 把這支 PR 掛到該工作包名下;不帶就沒有歸屬,owns 對誰都放行。
# wp-gate.sh check-deps {owner}/{repo} {wp-number} --analyze {分析頁頁名}
# 候選工作包能不能挑:活抓分析頁 WBS 表的相依欄,逐一核對每個相依工作包的狀態欄與
# PR 欄(PR 有給就活查 Gitea 是否已合併),不吃 implement 技能自己讀到的任何快取或判斷。