develop 進 master:CPM 甘特圖修正、工作包 PR 閘門平行化 #25
@@ -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={最新一筆留言的時間戳}` 供寫回分析頁。`lock {owner}/{repo} {index}` 在 PR 開好後轉呼叫 `sdlc-gate.sh wp-lock` 記下未結清。第一行固定 `status=...` 供程式判讀,其後為繁中說明;結束碼 `0`=已合併或無阻擋、`1`=未合併(擋住,呼叫端必須停下來逐筆修留言)、`2`=用法錯誤、`3`=相依工具或 PR 查不到(**查不到就擋,不放行**)。狀態檔由 `jsc-hooks` 管(`$JSC_HOME/wp/{owner}-{repo}.pr`,不綁 session,開新對話照樣擋),本檔只負責查詢與結清 |
|
||||
| `tools/wp-gate.sh` | 工作包 PR 閘門,把「一個工作包的 PR 沒合併,擋的是相依於它的工作包,不是整份分析」從內文敘述變成程式判定,共三個用法。`check {owner}/{repo} {index} [--since {ISO 時間}]` 查一支 PR:已合併就呼叫 `jsc-hooks` 的 `sdlc-gate.sh wp-unlock` 解鎖並印 `status=merged`;沒合併就印 `status=open` 或 `status=closed-unmerged`(被關掉但沒合併不算完成),接著把 issue 留言、審查評語、行內留言全部逐行印出(`--since` 只印更新的,值取上一輪的 `latest=`),最後一行印 `latest={最新一筆留言的時間戳}` 供寫回分析頁。`check-deps {owner}/{repo} {wp-number}` 查某個候選工作包能不能挑:活抓分析頁 WBS 表的相依欄,逐一核對每個相依工作包的狀態與 PR 是否已合併,只認同一張分析頁上的 `WP-NN` 編號,指到別份計畫的文字項目查不了就列出來要求人工確認,不當成擋人的理由。`lock {owner}/{repo} {index}` 在 PR 開好後轉呼叫 `sdlc-gate.sh wp-lock` 記下未結清(一個工作包一支鎖檔,可以同時記好幾筆平行進行的工作包)。第一行固定 `status=...` 供程式判讀,其後為繁中說明;結束碼 `0`=已合併或無阻擋(含 `check-deps` 的 `ready`)、`1`=未合併或 `check-deps` 判定 `blocked`、`2`=用法錯誤、`3`=相依工具或 PR/分析頁查不到(**查不到就擋,不放行**)。狀態檔由 `jsc-hooks` 管(`$JSC_HOME/wp/{owner}-{repo}-{index}.pr`,不綁 session,開新對話照樣擋),本檔只負責查詢與結清 |
|
||||
| `tools/stage-report.sh` | 階段收尾回報,四個階段共用。`stage-report.sh {plan\|analyze\|implement\|maintain}` 加 `--page TYPE:PAGE`(本階段寫過的每一頁,可重複)產出繁中回報:模型閘門判定(轉述 `sdlc-gate.sh report`,不自評)、工作日誌連結(`--worklog`、`--worklog-heading` 組出導向條目標題的錨點)、所有寫入的 wiki 絕對網址。實作階段再加 `--worktree`、`--source-branch`、`--work-branch`、`--target-branch`、`--pr`,來源分支在不在遠端、工作分支幾個 commit、推送了沒,都由腳本現查。沒有工作日誌時警告使用者檢查,並用 `--pending-file`、`--log-hash` 把內容交給 `jsc-log/tools/worklog-pending.sh` 暫存,下次寫日誌一併寫入。結束碼 `0`=完整、`1`=有警告(**警告不是阻擋**)、`2`=用法錯誤、`3`=相依工具找不到 |
|
||||
|
||||
## Skills 目錄
|
||||
@@ -41,7 +41,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
||||
|
||||
### `implement`
|
||||
|
||||
實作:先確認來源分支(同時是 PR 目標)→ 產生工作證鎖定「未完成、無相依、無工作證」的工作包(交付工作包優先)→ 交付工作包開工前先確認交付內容(API 文件、由使用者輸入,見 `references/deliver-formats.md`)→ **動程式碼前先從 `origin/{source-branch}` 建立 worktree**(`.worktree/{analysis-HASH}/{repo}`,分支處理依決策樹詢問)→ 在 worktree 內逐項 TDD 實作、每完成一項立即更新 wiki → 程式碼審查 → **每完成一個工作包就 commit、push、PR 回來源分支**(一包一 PR),接著 `tools/wp-gate.sh lock` 上鎖 → **PR 未合併就不開下一包,判定在程式層**:領工作包前先跑 `tools/wp-gate.sh check`,未合併就把全部留言逐筆印出並擋住,以 sub agent 回原 worktree 逐筆修正、推同一條工作分支(不開第二個 PR),修不動或純討論的留言忽略但要在回報裡列出,處理過的時間戳寫回分析頁的 PR 欄位當下一輪的 `--since`;合併後解鎖並移除 worktree → 詢問交付文件格式(`DELIVER_{HASH}` wiki 頁或 Gitea 議題留言)並產出 → 詢問是否加入維護目錄 → 階段回報(`tools/stage-report.sh`,多報工作目錄與來源、工作、目標三條分支)。
|
||||
實作:先確認來源分支(同時是 PR 目標)→ 領包前先跑 `tools/wp-gate.sh check` 把每個已開 PR 但沒合併的工作包留言逐筆印出並修正(以 sub agent 回原 worktree、推同一條工作分支,不開第二個 PR),**這一步只結清舊 PR 的留言,不擋別的工作包**——修不動或純討論的留言忽略但要在回報裡列出,處理過的時間戳寫回分析頁的 PR 欄位當下一輪的 `--since` → 產生工作證鎖定「未完成、無工作證」的候選工作包(交付工作包優先),**每個候選都跑 `tools/wp-gate.sh check-deps` 判斷能不能挑**:活查它在分析頁上的相依工作包是否都已合併,只有相依於它的包才會被一支未合併的 PR 擋住,跟它無關的工作包可以平行進行 → 交付工作包開工前先確認交付內容(API 文件、由使用者輸入,見 `references/deliver-formats.md`)→ **動程式碼前先從 `origin/{source-branch}` 建立 worktree**(`.worktree/{analysis-HASH}/{wp-number}/{repo}`,一個工作包一個 worktree,分支處理依決策樹詢問)→ 在 worktree 內逐項 TDD 實作、每完成一項立即更新 wiki → 程式碼審查 → **每完成一個工作包就 commit、push、PR 回來源分支**(一包一 PR),接著 `tools/wp-gate.sh lock` 上鎖,合併後解鎖並移除 worktree → 詢問交付文件格式(`DELIVER_{HASH}` wiki 頁或 Gitea 議題留言)並產出 → 詢問是否加入維護目錄 → 階段回報(`tools/stage-report.sh`,多報工作目錄與來源、工作、目標三條分支)。
|
||||
|
||||
### `maintain`
|
||||
|
||||
@@ -61,7 +61,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
|
||||
| `references/stage-report.md` | 階段回報:四階段都要交的三項(模型能力標籤、工作日誌連結、所有寫入的 wiki 連結)、實作階段多交的四項、沒寫日誌時的暫存規則、提前停止也要回報 |
|
||||
| `references/model-gate.md` | 模型閘門:執行順序、各階段必要標籤、阻擋與回報的鐵則 |
|
||||
| `references/tdd.md` | 接縫、紅綠循環規則、反模式 |
|
||||
| `references/branch.md` | 分支規則:**一律以遠端 `origin/{branch}` 為準、動作前先 fetch**、分析前確認來源分支、實作沿用同一條來源分支作為 PR 目標(一包一 PR、PR 未合併不開下一包,閘門分工見該檔)、實作一律在 `.worktree/{HASH}/{repo}` 內進行(建立前問分支、PR 合併後才移除)、判定遠端預設分支、不破壞未提交變更 |
|
||||
| `references/branch.md` | 分支規則:**一律以遠端 `origin/{branch}` 為準、動作前先 fetch**、分析前確認來源分支、實作沿用同一條來源分支作為 PR 目標(一包一 PR,一個工作包的 PR 沒合併只擋相依於它的工作包,不擋整份分析,閘門分工見該檔)、實作一律在 `.worktree/{HASH}/{wp-number}/{repo}` 內進行——一個工作包一個 worktree,讓互不相依的工作包能平行進行不互相搶路徑(建立前問分支、PR 合併後才移除)、判定遠端預設分支、不破壞未提交變更 |
|
||||
| `references/consensus.md` | 規劃與分析的提問規則:一輪不算問完、共識的兩個判定條件、未決項處理 |
|
||||
| `references/deliver-formats.md` | 交付內容型別:API 文件必備欄位、範例資料優先序、既有端點的新舊參數標示 |
|
||||
|
||||
|
||||
@@ -77,12 +77,13 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
|
||||
### 路徑
|
||||
|
||||
```
|
||||
{cwd}/.worktree/{analysis-HASH}/{repo}
|
||||
{cwd}/.worktree/{analysis-HASH}/{wp-number}/{repo}
|
||||
```
|
||||
|
||||
- `{analysis-HASH}`:`ANALYZE_{HASH}` 的 HASH 部分,不含 `ANALYZE_` 前綴。
|
||||
- `{wp-number}`:這次要做的工作包編號(例如 `WP-06`)。**一定要有這一層**:同一份分析常有好幾個互不相依的工作包平行進行,路徑少了這一層,兩個工作包會搶同一個目錄——第二個到的會被「目標路徑已存在」擋下,因為它不是同一份工作的 worktree。
|
||||
- `{repo}`:存取庫名稱,不含 owner(`HP/WebService.Buy` → `WebService.Buy`)。不同 owner 的同名存取庫同時出現時,才改用 `{owner}-{repo}` 避免蓋掉,並在輸出中說明。
|
||||
- 一份分析涉及多個存取庫時,每個存取庫各一個 worktree,並列在同一個 HASH 目錄下。
|
||||
- 一份分析涉及多個存取庫時,每個存取庫各一個 worktree,並列在同一個 `{wp-number}` 目錄下。
|
||||
|
||||
### 建立前先問分支怎麼處理
|
||||
|
||||
@@ -114,16 +115,18 @@ SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origi
|
||||
- worktree 內還有未提交變更時**不移除**,回報並停止——那些變更沒有進 PR,移除等於丟掉。
|
||||
- 同一份分析的多個 worktree 全部移除後,若 `.worktree/{HASH}/` 已空就一併刪掉那層目錄。
|
||||
|
||||
### PR 未合併就不開下一個工作包
|
||||
### 一個工作包的 PR 沒合併,擋的是相依於它的包,不是整份分析
|
||||
|
||||
一個工作包的 PR 還沒合併,就不得開始下一包。修正一律回原 worktree、推同一條工作分支,**不為同一包開第二個 PR**。
|
||||
一個工作包的 PR 還沒合併,這一包就沒結清:修正一律回原 worktree、推同一條工作分支,**不為同一包開第二個 PR**。
|
||||
|
||||
但「這一包沒結清」不等於「不得開始下一包」——SDLC 實作可能有好幾個互不相依的工作包平行進行,各自的 worktree、各自的 PR。真正被擋住的只有**相依於它的**工作包;跟它無關的工作包不受影響,可以另開一個工作階段(或另一個 worktree)同時進行。
|
||||
|
||||
這條規則在程式層強制,不靠內文自律,分工是兩層:
|
||||
|
||||
| 層 | 誰執行 | 做什麼 |
|
||||
| --- | --- | --- |
|
||||
| 工具閘門 | `tools/wp-gate.sh` | 唯一去問 Gitea 的一方,也是唯一有權解鎖的一方。`check` 查合併狀態、印出全部留言、印 `latest=` 時間戳;`lock` 在 PR 開好後記下未結清。結束碼 0 放行、1 擋住、2 用法錯誤、3 查不到(查不到就擋,不放行) |
|
||||
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `plan`、`analyze`、`maintain`,放行 `implement`——擋住的是「跳去做別的階段」,不是「回來把這一包做完」 |
|
||||
| 工具閘門 | `tools/wp-gate.sh` | 唯一去問 Gitea 的一方,也是唯一有權解鎖的一方。`check` 查合併狀態、印出全部留言、印 `latest=` 時間戳,只管那一支 PR 自己;`check-deps` 查某個候選工作包能不能挑,活抓分析頁的相依欄逐一核對;`lock` 在 PR 開好後記下未結清(一個工作包一支鎖檔)。結束碼 0 放行、1 擋住、2 用法錯誤、3 查不到(查不到就擋,不放行) |
|
||||
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `plan`、`analyze`、`maintain`,放行 `implement`——擋住的是「跳去做別的階段」,不是「回來把這一包做完」。這一層是整個存取庫共用的粗粒度提醒,不是逐工作包判斷,細粒度的相依判斷交給 `check-deps` |
|
||||
|
||||
狀態檔不綁 session:PR 沒合併就是沒合併,開新對話照樣擋。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: implement
|
||||
description: SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), then confirm the analysis page's source branch - it is both the worktree base and the PR target. An unmerged work package PR is a hard gate enforced in code by tools/wp-gate.sh: it reads every PR comment, sub agents fix them in the original worktree, and no next package starts until that PR merges. Claim a ready work package from ANALYZE_CONTENTS with a work ticket, confirm a delivery package's content type, then complete its TDD todos one at a time inside a worktree built from origin/{source-branch}, updating the wiki after every item, closing with jsc-review code-review, one PR back to the source branch, the chosen delivery document, an optional MAINTAIN_CONTENTS entry, and a tools/stage-report.sh report covering the model tag verdict, the worklog link, every wiki link written, the worktree and the three branches. Use when analysis is done and code must be written; not for planning or analysis.
|
||||
description: SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), then confirm the analysis page's source branch - it is both the worktree base and the PR target. Every already-open work package's PR comments get triaged via tools/wp-gate.sh check (sub agents fix them in that package's own worktree), but that never blocks a different, unrelated package - independent work packages can proceed in parallel. Picking a candidate is gated per-candidate in code by tools/wp-gate.sh check-deps, which re-reads the analysis page's WBS dependency column and queries Gitea itself: only a candidate whose own declared dependencies are merged may be claimed. Claim a ready work package from ANALYZE_CONTENTS with a work ticket, confirm a delivery package's content type, then complete its TDD todos one at a time inside a worktree built from origin/{source-branch}, updating the wiki after every item, closing with jsc-review code-review, one PR back to the source branch, the chosen delivery document, an optional MAINTAIN_CONTENTS entry, and a tools/stage-report.sh report covering the model tag verdict, the worklog link, every wiki link written, the worktree and the three branches. Use when analysis is done and code must be written; not for planning or analysis.
|
||||
---
|
||||
|
||||
# implement
|
||||
@@ -17,16 +17,20 @@ All wiki reads and writes go through `jsc-gitea:wiki`.
|
||||
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 the previous work package's PR before picking anything — the gate lives in code, not in this text**:
|
||||
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`) blocks. Fix every comment the script printed — issue comments, review verdicts and inline comments alike. **Each round of fixes MUST run as a sub agent** inside the original worktree, pushing to the same work branch, so the PR updates itself. Never open a second PR for the same package, and never ask whether to fix or wait: the gate already decided.
|
||||
3. Exit 1 (`status=open` or `status=closed-unmerged`) means that package's own PR is not settled yet. Fix every comment the script printed — issue comments, review verdicts and inline comments alike. **Each round of fixes MUST run as a sub agent** inside that package's own worktree, pushing to the same work branch, so the PR updates itself. Never open a second PR for the same package, and never ask whether to fix or wait: the gate already decided. **This does not block picking a different, unrelated work package** — SDLC implementation can run several independent packages in parallel; an unmerged PR only holds back packages that depend on it (step 6 checks that specifically), never the whole analysis page.
|
||||
4. Give every printed comment an outcome — fixed, no fix needed, or cannot fix. Ignore what you cannot fix, plus pure discussion and praise: do not reply on the PR and do not stop the flow, and list each ignored comment with its reason in your final report.
|
||||
5. Write the script's `latest=` value into that work package's PR column, appended after the existing PR link as `#{index} 已處理留言 {ISO time}`, and save the page back to the wiki. Next run passes it as `--since`, so handled comments stay handled. Reuse the existing PR column; the analysis page's columns belong to `analyze`.
|
||||
6. Exit 3 means the gate could not decide (a missing dependency, or the PR could not be found). Report it and stop — an undecidable gate never counts as merged.
|
||||
7. Completion condition: no work package holds an unmerged PR; stop here otherwise.
|
||||
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, dependency-free (or all dependencies done), and not holding a work ticket**. Completion condition: you have listed every selectable work package, or reported that none is selectable and stopped.
|
||||
6. **Step 4's gate comes first: while `wp-gate.sh check` exits 1, no work package may be picked.** Let the user pick a work package per `jsc-ask:ask` rules (options state open-item count and estimated effort). **List delivery packages (交付 `是`) first** — the analysis page makes `WP-01` the standalone delivery package, so keep that order in the options. Write the ticket into that work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki. Completion condition: the ticket is saved on the wiki; only then may you proceed.
|
||||
7. Completion condition: every work package holding an unmerged PR has had this run's comments triaged (fixed, no fix needed, or cannot fix) and reported; a package left unmerged after this does not block step 5/6 for packages that do not depend on it.
|
||||
5. Read `ANALYZE_CONTENTS` via `jsc-gitea:wiki` and list what is unfinished: plan name, HASH, work package number, count of open items. A selectable work package must satisfy all three: **unfinished, not holding a work ticket, and — verified by step 6's `wp-gate.sh check-deps`, never by eyeballing the 相依 column yourself — dependency-free or all dependencies merged**. Completion condition: you have listed every selectable work package, or reported that none is selectable and stopped.
|
||||
6. **Before picking, the candidate's own dependencies decide — the gate lives in code, not in this text, and it is scoped to that one candidate, never to every open PR on the page**:
|
||||
1. For the candidate work package the user is about to choose, run `jsc-sdlc/tools/wp-gate.sh check-deps {owner}/{repo} {wp-number}`. It re-reads the analysis page and queries Gitea itself — it does not trust anything you already read or concluded.
|
||||
2. Exit 0 (`status=ready`) → eligible; proceed to claim it. Exit 1 (`status=blocked`) → not eligible; report which dependency work package's PR is not merged yet, and offer the user the other eligible candidates instead of this one. Exit 3 (`status=missing-dep`) is an undecidable gate — report it and stop; never treat an undecidable result as either ready or blocked.
|
||||
3. A dependency phrased as text pointing outside this analysis page (another plan, another analysis) cannot be checked by the script; it comes back listed separately as needing manual confirmation. Confirm it with the user before proceeding — an unchecked cross-page dependency is not the same as a cleared one.
|
||||
4. Let the user pick per `jsc-ask:ask` rules from the eligible candidates only (options state open-item count and estimated effort). **List delivery packages (交付 `是`) first** — the analysis page makes `WP-01` the standalone delivery package, so keep that order in the options. Write the ticket into that work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki. Completion condition: `check-deps` returned `status=ready` for the picked candidate (any cross-page dependency confirmed with the user), the ticket is saved on the wiki; only then may you proceed.
|
||||
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`.
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
`WP-01` 固定是交付、交接工作包,獨立成一包,不與實作合併;用到它規格的實作工作包相依於它。
|
||||
交付型別於實作階段開工時確認(API 文件、由使用者輸入)。
|
||||
資料來源欄記錄範例資料的出處:`真實:{source}` 或 `推論:無來源`。
|
||||
PR 欄記錄該工作包的 PR 連結與編號;PR 未合併前不得開始下一個工作包。
|
||||
PR 欄記錄該工作包的 PR 連結與編號;一個工作包的 PR 未合併前,擋的是**相依於它**的工作包,不是整份分析——跟它無關的工作包可以平行進行,不必等它合併。
|
||||
相依欄只寫本頁的 `WP-NN` 編號,多個用頓號分隔(例:`WP-01、WP-03`);沒有相依填 `-`。`implement` 挑包時靠 `wp-gate.sh check-deps` 活查這一欄逐一核對,寫成別的格式(自然語言、別份計畫的敘述)它查不出來,只會被列成「需人工確認」。跨頁的相依(例如依賴另一份計畫的產出)本來就查不了,允許寫成文字,但盡量也帶出可辨識的來源(頁名、HASH)方便人工核對。
|
||||
|
||||
| 編號 | 工作包名稱 | 交付 | 交付型別 | 相依 | 工時(h) | 天數 | 資料來源 | 工作證 | PR | 狀態 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
|
||||
+126
-14
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
# wp-gate.sh — 工作包 PR 閘門:一個工作包的 PR 沒合併,就不准開下一包(供 jsc-sdlc:implement 呼叫)。
|
||||
# wp-gate.sh — 工作包 PR 閘門:一個工作包的 PR 沒合併,擋的是相依於它的工作包,不是整份分析
|
||||
# (供 jsc-sdlc:implement 呼叫;check-deps 判斷某個候選包能不能挑,check/lock 管單一 PR 本身)。
|
||||
#
|
||||
# 為什麼要有這支腳本:這條規則原本只寫在技能內文裡,靠模型自律遵守。內文靠不住——換一個
|
||||
# 工作階段、換一個模型,或只是上下文被截掉,規則就跟著消失,而且沒有任何徵兆看得出來。
|
||||
@@ -15,19 +16,26 @@
|
||||
# --since 只印比該時間更新的留言,值用上一輪印出的 latest=(UTC,形如 2026-08-25T10:19:59Z)。
|
||||
# wp-gate.sh lock {owner}/{repo} {index}
|
||||
# PR 開好之後上鎖,讓閘門跨工作階段有效。轉呼叫 sdlc-gate.sh wp-lock。
|
||||
# wp-gate.sh check-deps {owner}/{repo} {wp-number}
|
||||
# 候選工作包能不能挑:活抓分析頁 WBS 表的相依欄,逐一核對每個相依工作包的狀態欄與
|
||||
# PR 欄(PR 有給就活查 Gitea 是否已合併),不吃 implement 技能自己讀到的任何快取或判斷。
|
||||
# 只認同一張分析頁上的 WP-NN 編號;相依欄裡指到別份計畫或別頁分析的文字項目查不了,
|
||||
# 照樣放行但會在說明裡列出來,要求人工確認。
|
||||
#
|
||||
# 輸出: 第一行固定為 `status=...`(供程式判讀),其後為人類可讀的繁中說明。
|
||||
# status=merged 已合併,鎖已解除,可以挑下一個工作包
|
||||
# status=merged 已合併,鎖已解除,相依於它的工作包現在可以挑了
|
||||
# status=open PR 還開著,沒有合併
|
||||
# status=closed-unmerged PR 被關掉但沒有合併——這不算完成
|
||||
# status=locked 已記下這筆未結清的 PR
|
||||
# status=ready check-deps 專用:相依的工作包都已結清,可以挑
|
||||
# status=blocked check-deps 專用:至少一個相依工作包還沒結清,不得挑
|
||||
# status=usage 用法錯誤
|
||||
# status=missing-dep 相依腳本找不到,或這支 PR 查不到
|
||||
# status=missing-dep 相依腳本找不到、PR 查不到,或分析頁/WBS 列查不到
|
||||
# check 未合併時,最後一行固定為 `latest={最新一筆留言的時間戳}`(一筆留言都沒有就是 `latest=`),
|
||||
# 供呼叫端寫回分析頁,下一輪拿它當 --since,已處理過的留言就不會再處理一遍。
|
||||
#
|
||||
# 結束碼: 0=已合併或無阻擋 1=未合併(擋住,呼叫端必須停下來逐筆修留言)
|
||||
# 2=用法錯誤 3=相依工具或 PR 查不到
|
||||
# 結束碼: 0=已合併或無阻擋(含 check-deps 的 ready) 1=未合併,或 check-deps 判定 blocked
|
||||
# 2=用法錯誤 3=相依工具或 PR/分析頁查不到
|
||||
#
|
||||
# 陷阱:
|
||||
# - 「state=closed 但 merged=false」最容易被當成完成:那是 PR 被關掉、程式碼沒進去,
|
||||
@@ -39,8 +47,12 @@
|
||||
# 餵進帶 +08:00 這類偏移的值會比錯,所以只餵上一輪的 latest=。
|
||||
# - latest= 取的是「全部留言」裡最新的一筆,不是過濾後那幾筆。取過濾後的會在沒有新留言時
|
||||
# 倒退回舊時間戳,下一輪又把處理過的留言全部翻出來。
|
||||
# - 已合併時會呼叫 sdlc-gate.sh wp-unlock 解鎖;解鎖失敗照樣回報 merged 並 exit 0,但會多印
|
||||
# 一行警示與手動指令——鎖沒清掉,hook 會一直提醒下去。
|
||||
# - 已合併時會呼叫 sdlc-gate.sh wp-unlock {owner}/{repo} {index} 解鎖這一包;解鎖失敗照樣
|
||||
# 回報 merged 並 exit 0,但會多印一行警示與手動指令——鎖沒清掉,hook 會一直提醒下去。
|
||||
# - check-deps 只核對「同一張分析頁上的 WP-NN 編號」,不是「相依欄裡的每一個字」:相依欄
|
||||
# 若寫的是另一份計畫或另一張分析頁的敘述(例如「節點建置」這種跨頁文字依賴),這支腳本
|
||||
# 沒有能力去查那邊的狀態,只能原樣列出來要求人工確認,不會拿它當擋人的理由——結構上就
|
||||
# 查不到的東西當成擋人的理由,跟前一條「查不到就擋」矛盾,會讓那個工作包永遠挑不到。
|
||||
set -u
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
@@ -50,8 +62,9 @@ usage() {
|
||||
cat >&2 <<'EOF'
|
||||
用法:
|
||||
wp-gate.sh check {owner}/{repo} {index} [--since {ISO 時間}] 查 PR:合併就解鎖放行,沒合併就印出留言並擋住
|
||||
wp-gate.sh check-deps {owner}/{repo} {wp-number} 候選工作包能不能挑:核對它在分析頁上的相依工作包是否都已結清
|
||||
wp-gate.sh lock {owner}/{repo} {index} 開完 PR 後上鎖,讓閘門跨工作階段有效
|
||||
結束碼: 0=已合併或無阻擋 1=未合併(擋住) 2=用法錯誤 3=相依工具或 PR 查不到
|
||||
結束碼: 0=已合併或無阻擋(含 check-deps 的 ready) 1=未合併,或 check-deps 判定 blocked 2=用法錯誤 3=相依工具或 PR/分析頁查不到
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -153,13 +166,14 @@ case "$sub" in
|
||||
|
||||
if [ "$merged" = 'true' ]; then
|
||||
echo 'status=merged'
|
||||
echo "$repo 第 $index 號 PR 已合併,這一包結清了,可以挑下一個工作包。"
|
||||
echo "$repo 第 $index 號 PR 已合併,這一包結清了,相依於它的工作包現在可以挑了。"
|
||||
gate=$(sdlc_gate_sh) || missing_dep "找不到 jsc-hooks 的 hooks/sdlc-gate.sh,鎖解不掉。並排版面請確認 {workspace}/hooks 存在,或設定 JSC_HOOKS_DIR 指向它的 hooks 目錄。"
|
||||
# stdin 一定要關掉:sdlc-gate.sh 的 hook 模式會讀標準輸入,管線沒人關閉時整支卡死。
|
||||
if sh "$gate" wp-unlock "$repo" >/dev/null 2>&1 </dev/null; then
|
||||
echo "鎖已解除($repo)。"
|
||||
# 鎖檔一個工作包一支,解鎖要帶 index,只解掉這一包;其餘平行進行的工作包不受影響。
|
||||
if sh "$gate" wp-unlock "$repo" "$index" >/dev/null 2>&1 </dev/null; then
|
||||
echo "鎖已解除($repo 第 $index 號)。"
|
||||
else
|
||||
echo "[jsc][工作包閘門][WARN]:鎖解不掉($repo)。PR 確實已合併,但狀態檔還在,hook 會一直提醒。請手動執行:sdlc-gate.sh wp-unlock $repo" >&2
|
||||
echo "[jsc][工作包閘門][WARN]:鎖解不掉($repo 第 $index 號)。PR 確實已合併,但狀態檔還在,hook 會一直提醒。請手動執行:sdlc-gate.sh wp-unlock $repo $index" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
@@ -167,11 +181,12 @@ case "$sub" in
|
||||
if [ "$state" = 'closed' ]; then
|
||||
echo 'status=closed-unmerged'
|
||||
echo "$repo 第 $index 號 PR 被關掉了,但沒有合併——程式碼沒進到來源分支,這不算完成。"
|
||||
echo '要嘛重開這支 PR 並把留言修完,要嘛請使用者裁決;在那之前不得挑下一個工作包。'
|
||||
echo '要嘛重開這支 PR 並把留言修完,要嘛請使用者裁決;在那之前這一包不算結清。'
|
||||
else
|
||||
echo 'status=open'
|
||||
echo "$repo 第 $index 號 PR 還開著,沒有合併。這一包還沒結清,不得挑下一個工作包。"
|
||||
echo "$repo 第 $index 號 PR 還開著,沒有合併。這一包還沒結清。"
|
||||
fi
|
||||
echo '這一包沒結清,只表示相依於它的工作包不能開始;其餘互不相依的工作包能不能挑,見 wp-gate.sh check-deps。'
|
||||
echo '下列每一筆留言都要有結果(已修、不需修、修不動)。修不動或純討論、讚美的留言直接忽略:'
|
||||
echo '不在 PR 上回覆、也不因此停下流程,但要在最終回報裡逐筆列出被忽略的留言與理由。'
|
||||
if [ -n "$since" ]; then
|
||||
@@ -196,6 +211,103 @@ case "$sub" in
|
||||
rm -f "$tmp"
|
||||
exit 1 ;;
|
||||
|
||||
check-deps)
|
||||
repo="${1:-}"; wp_number="${2:-}"
|
||||
valid_repo "$repo" || { echo 'status=usage'; usage; exit 2; }
|
||||
valid_index "$wp_number" || { echo 'status=usage'; usage; exit 2; }
|
||||
[ "$#" -le 2 ] || { echo 'status=usage'; usage; exit 2; }
|
||||
|
||||
gitea=$(gitea_sh) || missing_dep "找不到 jsc-gitea 的 tools/gitea.sh。並排版面請確認 {workspace}/gitea 存在,已安裝版面請確認 jsc-gitea plugin 已安裝,或設定 JSC_GITEA_TOOLS 指向它的 tools 目錄。"
|
||||
|
||||
hash=$(sh "$gitea" hash-id "$repo" 2>/dev/null)
|
||||
[ -n "$hash" ] || missing_dep "算不出 $repo 的 wiki HASH。"
|
||||
wiki_repo=$(sh "$gitea" wiki-repo ANALYZE 2>/dev/null)
|
||||
[ -n "$wiki_repo" ] || missing_dep "解析不出 ANALYZE 頁的 wiki 存取庫(JSC_WIKI_REPO_ANALYZE 與 JSC_WIKI_REPO 都未設定)。"
|
||||
page="ANALYZE_${hash}"
|
||||
content=$(sh "$gitea" wiki-get "$wiki_repo" "$page" 2>/dev/null)
|
||||
[ -n "$content" ] || missing_dep "讀不到分析頁 $wiki_repo 的 $page。"
|
||||
|
||||
# WBS 表欄位(以 | 分隔,$1 是第一個 | 之前的空字串):
|
||||
# $2=編號 $6=相依 $11=PR $12=狀態。
|
||||
# 找列一律比數值,不比字串:範本固定兩位數補零(WP-08),但呼叫端或相依欄裡的寫法
|
||||
# 不一定補零(WP-8);比字串會讓兩種寫法各自找不到對方那一列,誤判成「查不到」。
|
||||
find_wp_row() { # $1=WP 編號(可帶或不帶前導零) -> 印出該列,找不到印空字串
|
||||
_n=$(printf '%s' "$1" | sed 's/^0*//'); [ -n "$_n" ] || _n=0
|
||||
printf '%s\n' "$content" | awk -F'|' -v n="$_n" '
|
||||
{ w = $2; gsub(/^[ \t]+|[ \t]+$/, "", w)
|
||||
if (w ~ /^WP-[0-9]+$/) {
|
||||
wn = w; sub(/^WP-0*/, "", wn); if (wn == "") wn = "0"
|
||||
if (wn + 0 == n + 0) { print; exit }
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
row=$(find_wp_row "$wp_number")
|
||||
[ -n "$row" ] || missing_dep "分析頁 $page 上找不到 WP-$wp_number 這一列,核對不了相依。"
|
||||
self_wp=$(printf '%s' "$row" | awk -F'|' '{ w = $2; gsub(/^[ \t]+|[ \t]+$/, "", w); print w }')
|
||||
|
||||
deps=$(printf '%s' "$row" | awk -F'|' '{ d = $6; gsub(/^[ \t]+|[ \t]+$/, "", d); print d }')
|
||||
case "$deps" in
|
||||
''|-|無)
|
||||
echo 'status=ready'
|
||||
echo "$self_wp 沒有相依,可以挑。"
|
||||
exit 0 ;;
|
||||
esac
|
||||
|
||||
unmet=''
|
||||
unknown=''
|
||||
# 用 sed 把分隔符號換成換行、靠 IFS 只切換行:頓號是多位元組字元,tr 逐位元組處理
|
||||
# 這種字元集合會把緊鄰的中文字元切壞(例如「節點建置」被腰斬成亂碼)。
|
||||
_old_ifs=$IFS
|
||||
IFS='
|
||||
'
|
||||
for dep in $(printf '%s\n' "$deps" | sed 's/、/\
|
||||
/g; s/,/\
|
||||
/g; s/,/\
|
||||
/g'); do
|
||||
IFS=$_old_ifs
|
||||
dep_idx=$(printf '%s' "$dep" | sed -n 's/.*WP-\([0-9][0-9]*\).*/\1/p')
|
||||
if [ -z "$dep_idx" ]; then
|
||||
# 不是本頁的 WP-NN 編號(例如指到別份計畫、別張分析頁的文字敘述):查不了,
|
||||
# 但不能因為查不了就一律擋住——那會讓這種工作包永遠挑不到。列出來要求人工確認。
|
||||
[ -n "$dep" ] && unknown="${unknown}${unknown:+、}$dep"
|
||||
continue
|
||||
fi
|
||||
dep_row=$(find_wp_row "$dep_idx")
|
||||
if [ -z "$dep_row" ]; then
|
||||
unmet="${unmet}${unmet:+、}WP-$dep_idx(分析頁上查不到這一列)"
|
||||
continue
|
||||
fi
|
||||
dep_wp=$(printf '%s' "$dep_row" | awk -F'|' '{ w = $2; gsub(/^[ \t]+|[ \t]+$/, "", w); print w }')
|
||||
dep_status=$(printf '%s' "$dep_row" | awk -F'|' '{ s = $12; gsub(/^[ \t]+|[ \t]+$/, "", s); print s }')
|
||||
if [ "$dep_status" = '已完成' ]; then
|
||||
continue
|
||||
fi
|
||||
dep_pr=$(printf '%s' "$dep_row" | awk -F'|' '{ print $11 }')
|
||||
pr_index=$(printf '%s' "$dep_pr" | sed -n 's#.*/pulls/\([0-9][0-9]*\).*#\1#p' | head -n1)
|
||||
if [ -z "$pr_index" ]; then
|
||||
unmet="${unmet}${unmet:+、}${dep_wp}(狀態「${dep_status:-未完成}」,還沒開 PR)"
|
||||
continue
|
||||
fi
|
||||
st=$(sh "$gitea" pr-status "$repo" "$pr_index" 2>/dev/null) || st=''
|
||||
merged=$(printf '%s' "$st" | awk '{print $2}')
|
||||
if [ "$merged" != 'true' ]; then
|
||||
unmet="${unmet}${unmet:+、}${dep_wp}(第 $pr_index 號 PR 未合併)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$unmet" ]; then
|
||||
echo 'status=blocked'
|
||||
echo "$self_wp 相依 $unmet 尚未結清,不得挑選。"
|
||||
[ -n "$unknown" ] && echo "另有查不了的相依項目(非本頁工作包編號,需人工確認):$unknown"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'status=ready'
|
||||
echo "$self_wp 在本頁上的相依工作包全部已結清,可以挑。"
|
||||
[ -n "$unknown" ] && echo "有查不了的相依項目(非本頁工作包編號,需人工確認是否已完成):$unknown"
|
||||
exit 0 ;;
|
||||
|
||||
lock)
|
||||
repo="${1:-}"; index="${2:-}"
|
||||
valid_repo "$repo" || { echo 'status=usage'; usage; exit 2; }
|
||||
|
||||
Reference in New Issue
Block a user