feat/wp-schedule-and-board/main #31

Merged
admin merged 17 commits from feat/wp-schedule-and-board/main into master 2026-09-17 06:09:42 +00:00
Member

摘要

把工作包排上時程與看板:建立相依、依拓撲排序推算截止日、掛 Milestone、歸入看板、記下人天估算。

需求議題

#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組

工作包議題

#8 — 建立工作包的相依、時程、看板與人天估算

變更內容

  • scripts/schedule.js — 依相依關係拓撲推算截止日(純運算,不碰 Gitea)。
  • scripts/issue-link.js — 建立阻擋/先決相依。
  • scripts/issue-update.js — 掛 Milestone、寫截止日、記人天估算。
  • scripts/project-add.js — 放進看板,看板 id 靠掃議題反查。
  • scripts/issue-body.js — 新增 upsertLineInSection(段落內就地更新一行)。
  • scripts/lib.js — parseIndex 抽成共用。
  • prompts/sdlc-analyze.md — 加入第三段「排上時程與看板」。
  • 測試 204 個案例(本 PR 新增 65 個)。

設計重點

  • 先算後寫。 schedule 純運算、沒有前置檢查也不需要登入;算出來的日期再交給三支寫入腳本逐顆寫上去。這樣時程邏輯可以在沒有 Gitea 的環境裡測到底。
  • 拓撲排序保證「截止日不早於先決」。 人工排時程最常見的矛盾就是前置工作比後續還晚到期。成環時直接報錯並列出環上成員——那代表拆法有問題,硬排沒意義。
  • 日期只算日曆日,不跳週末也不扣假日。 跳過哪些日子是團隊政策,工具不替使用者決定。
  • projects 是整份取代不是附加,所以 project-add 先讀出議題既有的看板再聯集。漏掉就等於把議題踢出原本的看板。
  • 相依請求必須帶 owner 與 repo,少了它們 Gitea 回 404 且訊息是「repository does not exist」,不看文件會以為路徑寫錯。
  • 三支寫入腳本都是冪等的:相依已存在就跳過、已在看板上就不重發、估算沒變就不改 body。

解決的問題

工作包原本開完就是一堆平鋪的議題,看不出先後。補上相依與時程之後,看板呈現的才是真實的開發順序。

影響的功能

新增。issue-extract 等四支腳本改用共用的 parseIndex,行為不變。

測試結果

npm test:

ℹ tests 204
ℹ suites 0
ℹ pass 204
ℹ fail 0

十七顆 commit 逐一 checkout 後跑測試,每一顆都是綠的。

拿這個專案自己的相依圖當真實資料(議題 #2–#17 的實際關係,16 顆工作包)跑排程:

拓撲順序: [2, 3, 4, 16, 5, 17, 6, 7, 8, 9, 10, 11, 12, 15, 13, 14]
  # 2  2026-09-22  1人天   # 9  2026-10-05  2人天
  # 3  2026-09-25  3人天   #10  2026-10-08  2人天
  ...                      #14  2026-10-18  3人天
最晚完成: 2026-10-18

成環時:{"ok":false,"error":{"code":"CYCLE_DETECTED","message":"相依關係成環,這些工作包彼此卡住:1、2"}}

三支寫入腳本對真實 Gitea 試跑(皆未寫入):

$ node scripts/issue-link.js --repo plugins/tea-sdlc --index 8 --depends 3,7 --dry-run
{"ok":true,...,"added":{"depends":[3],...},"skipped":{"depends":[7],...}}   ← #7 早就建好了

$ node scripts/issue-update.js --repo plugins/tea-sdlc --index 8 --milestone "第一階段" --dry-run
{"ok":false,"error":{"code":"UNKNOWN_MILESTONE","message":"...本工具不建立 Milestone..."}}

$ node scripts/project-add.js --repo plugins/tea-sdlc --index 8 --project "開發看板" --dry-run
{"ok":false,"error":{"code":"PROJECT_NOT_FOUND","message":"最近的議題沒有任何一顆掛在看板上...請直接貼專案網址"}}

⚠ 驗收標準第 5 條有一半做不到

人天估算同時寫入議題估算欄位(秒)與 body 內人類可讀的一份

前半做不到。 Gitea 1.27 的 API 沒有任何請求定義接受 time_estimate:

  • 該欄位只出現在 Issue 的回應 schema 裡。
  • EditIssueOption 與 CreateIssueOption 都沒有它。
  • /issues/{index}/times 與 /stopwatch/* 只能記實際工時,不是估算。
  • 整份 swagger 搜尋 estimate 只有兩個命中,都在 Issue 回應內。

兩軸 review 各自獨立查證過,結論一致。議題估算欄位只能靠人在網頁上填。

因此 --estimate-days 只把估算寫成 body 裡的 估算人天:N(放在「關聯」段落),sdlc-report 之後要比對估算與實際工時時讀的也是這一行。正本裡另立一節寫明這個限制。

這一條的 checkbox 我留著沒勾。 議題 #1 的「Gitea 承載對應」表把 Issue.time_estimate(秒)+ body 寫成兩者都可寫,那一格需要更正——要不要我去改 #1,或在 #1 留一則說明,你說一聲。

Code review 修掉的四個缺陷(全部先重現再修)

  1. upsertLineInSection 會改壞別人的內容,三種情境:圍欄裡的 ## 關聯 被當成真標題;## 關聯度說明 被 indexOf 誤認成 ## 關聯;「這一行是否已存在」用整份 body 比對,於是別的段落有同前綴時被改掉、真正的關聯段落反而拿不到值。改成沿用同檔的圍欄判斷、標題完全相符、只在段落範圍內找。
  2. schedule 不擋重複的 index:相依看後者、標題與人天取前者,混出來的時程還回傳 ok:true。
  3. parseIndex 抄了四份,抽進 lib。
  4. project-add 有一條恆真的測試(「沒有非 GET 請求打到路徑含 project 的端點」,但 Gitea 的專案根本沒有 API 路徑),壞掉的實作也會通過。已改成驗真正的寫入只有議題那一次 PATCH。

另外,第一版寫的三條回歸測試裡有兩條其實是陪跑的——情境裡沒有後續段落,正確與錯誤的結果剛好都落在 body 結尾。加上真正的後續段落之後才有鑑別力;現在三條都確認過會在舊版實作上失敗。


🤖 Generated with Claude Code

## 摘要 把工作包排上時程與看板:建立相依、依拓撲排序推算截止日、掛 Milestone、歸入看板、記下人天估算。 ## 需求議題 #1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組 ## 工作包議題 #8 — 建立工作包的相依、時程、看板與人天估算 ## 變更內容 - `scripts/schedule.js` — 依相依關係拓撲推算截止日(純運算,不碰 Gitea)。 - `scripts/issue-link.js` — 建立阻擋/先決相依。 - `scripts/issue-update.js` — 掛 Milestone、寫截止日、記人天估算。 - `scripts/project-add.js` — 放進看板,看板 id 靠掃議題反查。 - `scripts/issue-body.js` — 新增 `upsertLineInSection`(段落內就地更新一行)。 - `scripts/lib.js` — `parseIndex` 抽成共用。 - `prompts/sdlc-analyze.md` — 加入第三段「排上時程與看板」。 - 測試 204 個案例(本 PR 新增 65 個)。 ## 設計重點 - **先算後寫。** `schedule` 純運算、沒有前置檢查也不需要登入;算出來的日期再交給三支寫入腳本逐顆寫上去。這樣時程邏輯可以在沒有 Gitea 的環境裡測到底。 - **拓撲排序保證「截止日不早於先決」。** 人工排時程最常見的矛盾就是前置工作比後續還晚到期。成環時直接報錯並列出環上成員——那代表拆法有問題,硬排沒意義。 - **日期只算日曆日,不跳週末也不扣假日。** 跳過哪些日子是團隊政策,工具不替使用者決定。 - **`projects` 是整份取代不是附加**,所以 `project-add` 先讀出議題既有的看板再聯集。漏掉就等於把議題踢出原本的看板。 - **相依請求必須帶 owner 與 repo**,少了它們 Gitea 回 404 且訊息是「repository does not exist」,不看文件會以為路徑寫錯。 - 三支寫入腳本都是冪等的:相依已存在就跳過、已在看板上就不重發、估算沒變就不改 body。 ## 解決的問題 工作包原本開完就是一堆平鋪的議題,看不出先後。補上相依與時程之後,看板呈現的才是真實的開發順序。 ## 影響的功能 新增。`issue-extract` 等四支腳本改用共用的 `parseIndex`,行為不變。 ## 測試結果 `npm test`: ``` ℹ tests 204 ℹ suites 0 ℹ pass 204 ℹ fail 0 ``` 十七顆 commit 逐一 checkout 後跑測試,每一顆都是綠的。 **拿這個專案自己的相依圖當真實資料**(議題 #2–#17 的實際關係,16 顆工作包)跑排程: ``` 拓撲順序: [2, 3, 4, 16, 5, 17, 6, 7, 8, 9, 10, 11, 12, 15, 13, 14] # 2 2026-09-22 1人天 # 9 2026-10-05 2人天 # 3 2026-09-25 3人天 #10 2026-10-08 2人天 ... #14 2026-10-18 3人天 最晚完成: 2026-10-18 ``` 成環時:`{"ok":false,"error":{"code":"CYCLE_DETECTED","message":"相依關係成環,這些工作包彼此卡住:1、2"}}` 三支寫入腳本對真實 Gitea 試跑(皆未寫入): ``` $ node scripts/issue-link.js --repo plugins/tea-sdlc --index 8 --depends 3,7 --dry-run {"ok":true,...,"added":{"depends":[3],...},"skipped":{"depends":[7],...}} ← #7 早就建好了 $ node scripts/issue-update.js --repo plugins/tea-sdlc --index 8 --milestone "第一階段" --dry-run {"ok":false,"error":{"code":"UNKNOWN_MILESTONE","message":"...本工具不建立 Milestone..."}} $ node scripts/project-add.js --repo plugins/tea-sdlc --index 8 --project "開發看板" --dry-run {"ok":false,"error":{"code":"PROJECT_NOT_FOUND","message":"最近的議題沒有任何一顆掛在看板上...請直接貼專案網址"}} ``` ## ⚠ 驗收標準第 5 條有一半做不到 > 人天估算同時寫入議題估算欄位(秒)與 body 內人類可讀的一份 **前半做不到。** Gitea 1.27 的 API 沒有任何請求定義接受 `time_estimate`: - 該欄位只出現在 `Issue` 的**回應** schema 裡。 - `EditIssueOption` 與 `CreateIssueOption` 都沒有它。 - `/issues/{index}/times` 與 `/stopwatch/*` 只能記**實際**工時,不是估算。 - 整份 swagger 搜尋 `estimate` 只有兩個命中,都在 `Issue` 回應內。 兩軸 review 各自獨立查證過,結論一致。議題估算欄位只能靠人在網頁上填。 因此 `--estimate-days` 只把估算寫成 body 裡的 `估算人天:N`(放在「關聯」段落),`sdlc-report` 之後要比對估算與實際工時時讀的也是這一行。正本裡另立一節寫明這個限制。 **這一條的 checkbox 我留著沒勾。** 議題 #1 的「Gitea 承載對應」表把 `Issue.time_estimate(秒)+ body` 寫成兩者都可寫,那一格需要更正——要不要我去改 #1,或在 #1 留一則說明,你說一聲。 ## Code review 修掉的四個缺陷(全部先重現再修) 1. **`upsertLineInSection` 會改壞別人的內容**,三種情境:圍欄裡的 `## 關聯` 被當成真標題;`## 關聯度說明` 被 `indexOf` 誤認成 `## 關聯`;「這一行是否已存在」用整份 body 比對,於是別的段落有同前綴時被改掉、真正的關聯段落反而拿不到值。改成沿用同檔的圍欄判斷、標題完全相符、只在段落範圍內找。 2. **`schedule` 不擋重複的 `index`**:相依看後者、標題與人天取前者,混出來的時程還回傳 `ok:true`。 3. `parseIndex` 抄了四份,抽進 lib。 4. `project-add` 有一條**恆真的測試**(「沒有非 GET 請求打到路徑含 project 的端點」,但 Gitea 的專案根本沒有 API 路徑),壞掉的實作也會通過。已改成驗真正的寫入只有議題那一次 PATCH。 另外,第一版寫的三條回歸測試裡**有兩條其實是陪跑的**——情境裡沒有後續段落,正確與錯誤的結果剛好都落在 body 結尾。加上真正的後續段落之後才有鑑別力;現在三條都確認過會在舊版實作上失敗。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jiantw83 added 17 commits 2026-09-17 06:07:07 +00:00
保證一件事:任一工作包的截止日都不早於它的先決。人工排時程最常出現的矛盾就是
前置工作比後續還晚到期,看板上看起來合理、實際上做不到。

以 Kahn 演算法排序,排不完就代表有環,而環上的成員正是排不進去的那些,直接把
它們列出來——相依成環是拆法有問題,硬排沒有意義。

日期以日曆日累加,不跳週末也不扣假日:跳過哪些日子是團隊政策,這裡不替使用者
決定。這支腳本不碰 Gitea 也不碰 git,純算數字,因此沒有前置檢查也不需要登入。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
除了逐例比對日期,另有一條測試直接驗「所有先決關係都滿足」這個不變式,
而不是只比對硬編的日期字串——不變式壞掉時它會指出是哪一對。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
--depends 是「這顆被誰擋住」,--blocks 是「這顆擋住誰」。先讀現況只補缺的那幾條,
重跑不會在 Gitea 上堆出重複的相依。

請求必須帶 owner 與 repo:Gitea 的相依端點少了它們會回 404,而且訊息是「repository
does not exist」,不看文件會以為是路徑寫錯。

自己依賴自己擋在發出請求之前——Gitea 會接受,但那會讓後續的拓撲排序永遠排不完。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
三件事經同一個 PATCH 送出,因為排時程時它們幾乎總是一起改,分開發等於多兩次往返。

Milestone 只認既有的:指到不存在的就中止並列出可選項目,本工具不建立 Milestone。

人天估算只寫進 body 的人類可讀一行。Gitea 1.27 的 API 沒有任何請求定義接受
time_estimate——它只出現在 Issue 的回應裡——所以議題的估算欄位無法由 API 寫入。
新增的 upsertLineInSection 負責就地更新那一行:重跑改估算不會累積成兩行,值沒變
就不把 body 塞進 PATCH,免得在議題上留下一筆沒有內容的編輯紀錄。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gitea 1.27 沒有「列出專案」的 endpoint,名稱只能反查:掃最近 50 筆議題,從它們
身上的 projects 欄位湊出 id→名稱對照。全都沒掛看板時湊不出來,這時請使用者直接
貼專案網址,結尾即 id。

projects 欄位是整份取代不是附加,所以要先讀出議題既有的看板再聯集——漏掉就等於
把這顆議題踢出原本的看板。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
先算後寫:schedule 算出截止日,再由 issue-link、issue-update、project-add 逐顆
補上相依、時程與看板。三支都先試跑再實跑,三支都是冪等的。

另立一節寫明人天估算的 API 限制,而不是把它藏在行文裡——estimate 只寫得進 body,
sdlc-report 之後讀的也是那一行,這件事踩到才知道就太晚了。

邊界改成三段各自列:哪一段不做什麼講清楚,兩顆工作包才不會互相踩。

Closes #8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
三個實際重現過的污染情境:

- 圍欄裡的 `## 關聯` 被當成真標題,估算插進圍欄後面。本檔的共同前提是「圍欄裡的
  東西不是內容」,這支卻自己用 indexOf 找標題,繞過了那個判斷。
- `## 關聯度說明` 被 indexOf 當成 `## 關聯` 命中,改到別人的段落。
- 「這一行是否已存在」用整份 body 比對,於是別的段落剛好有 `估算人天:` 時被改掉,
  真正的關聯段落反而一直拿不到值。

改成沿用同檔的 eachLine 走行、標題要完全相同、既有那一行只在段落範圍內找。
弄錯的代價是靜靜改壞別人的內容,所以三道判斷都收緊。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
同一個 index 出現兩次時,相依看的是後者、標題與人天卻取到前者,算出來的時程是
兩份定義混出來的東西,而且回傳 ok:true 完全不報錯。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
同一段 /^[1-9]\d*$/ 與 BAD_INDEX 已經抄了四份。lib 本來就放著同性質的 parseRepo,
這一支該待在它旁邊。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
三條測試都先確認過會在舊版實作上失敗。第一版寫出來時有兩條其實是陪跑的——
情境裡沒有後續段落,正確與錯誤的結果剛好都落在 body 結尾,分不出來。加上一個
真正的後續段落之後才有鑑別力。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
原本的條件是「沒有非 GET 請求打到路徑含 project 的端點」,但 Gitea 的專案根本
沒有 API 路徑,這個條件恆真,壞掉的實作也會通過。改成斷言真正的寫入只有議題本身
那一次 PATCH、而且只帶 projects 欄位。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-09-17 06:09:38 +00:00
admin merged commit 34d5890126 into master 2026-09-17 06:09:42 +00:00
admin deleted branch feat/wp-schedule-and-board/main 2026-09-17 06:09:42 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/tea-sdlc#31