jiantw83 and Claude Opus 5
3da822adc6
docs(references): 新增委派判準,四條全部成立才可委派
...
只在意結果的步驟——翻譯分支名、算截止日、逐條比對可行性清單、產生 HTML 總覽——
它們的中間產物目前全部留在主脈絡裡,把後面真正需要判斷力的步驟愈擠愈窄。要把這些
交出去,得先說清楚「哪些交得出去」,否則下一個人只能憑感覺標。
四條判準裡有兩條是硬排除,各自寫上理由,因為沒有理由的規則遲早會被繞過去:
第二條(步驟中不會詢問使用者)是因為子代理問不到使用者,一旦卡在提問就只能自行決定;
第四條(不直接寫入 Gitea 或 git)不是因為子代理做不好,而是**它的失敗沒有人看著**
——備妥工作樹失敗會中止整個領取、實際提交失敗會留下半套 git 歷史。
委派一律以**能力描述**表達,不指名任何平台的工具:子代理是平台專屬能力,而流程正本
必須保持平台中立(#4 的驗收標準)。能力描述對不支援的平台是自然降級,同一份正本兩邊
都讀得通,不必維護兩份。這份正本明寫「不要把它改寫成工具名」,因為那是讀到它的人
最可能動手改的地方。
那張「目前標記為〔可委派〕的步驟」表與正本上的標記互為正本,由資產測試雙向綁住。
議題 #58
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-18 09:12:08 +08:00
jiantw83 and Claude Opus 5
46c6db8c70
feat(time-log): 對既有議題重跑時補到當下,每一輪各記一筆
...
原本的終點一律是議題的建立時間,所以對既有議題重跑時相減為負,補登直接 no-op——
那一輪的規劃時間就這樣掉了,而那正是這顆議題要修的毛病,只是換個位置出現。
終點改成看議題是不是這一輪建立的:是就補到議題建立那一刻(第一次跑),不是就補到
補登的當下(重跑)。回報多出 `迄` 與 `依據` 兩個欄位,讓人一眼看出這一筆補的是哪一段。
「這顆議題上已經有工時就跳過」這條規則拿掉了,它與累計互斥。防重複只剩一條:錶已經
跑在這顆議題上——那一段已經有錶在記,補下去會與錶涵蓋的區間重疊。連帶把 lib 的
listIssueTimes 一起移除,沒有人再用它。
實跑驗過(議題 #57):補登 121 秒、起錶、停錶 7 秒,兩筆都進得了週報,驗完刪除。
議題 #57 的規格同步更正,另外兩處過期的敘述也一併改掉。
議題 #57
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-18 08:46:27 +08:00
jiantw83 and Claude Opus 5
7ee050de2f
docs(sdlc-plan,sdlc-analyze): 兩份正本各自起停自己的錶
...
起錶與停錶寫在同一份正本裡成對出現,讀的人一眼看得出這段計時涵蓋到哪;兩份各加一節
「計時範圍」把兩端指出來。
sdlc-plan 記兩段:議題建立之前那段在第一步記下開始時間、議題建立之後以補登記上去;
之後起錶,回報那一步停錶。補登排在議題建立之後、起錶之前,順序反過來的話補登會被
time-log 當成「這一步做過了」而跳過。
sdlc-analyze 在它分析的那顆需求議題上起錶,起點放在第一段開頭——分析最耗時的正是
共識之前那一段,從第二段才起的話那段時間永遠是零。因此「共識摘要之前不對 Gitea
產生任何寫入」改寫成「不寫入任何**內容**」,並把碼錶明文除外、寫上理由:它記的是
工時,不是內容。這一條與 repo 擁有者確認過。
sdlc-feat 的「碼錶一律由使用者自己停」收斂成「**別顆議題上的**錶一律由使用者自己停」
——它原本讀起來像全域規則,而現在每道指令都會停自己起的那一支。
正本之間改以步驟**名稱**互指,不用編號:編號會整批位移,名字不會。測試跟著改用新的
promptStep 輔助函式,以名字框出某一步的內容,別人插一步時不會無聲地框到另一段上。
議題 #57
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 19:32:37 +08:00
jiantw83 and Claude Opus 5
81d02bffcf
feat(timer,time-log): 起錶配上停錶,並補登議題建立之前的時間
...
碼錶只在領取工作包時起動,所以工時報表上規劃與分析永遠是零。久了會讓人以為
規劃不花時間,而那正是估算失準最常見的來源。
timer 加上 --stop,而且**只停 --index 指的那一顆**。每個階段停掉自己起的那一支,
錶就不會跨階段跑——跑完就去開會而錶跑一整天,報表當場失真。反過來,別顆議題上的錶
一律不碰:Gitea 在別顆議題上起新錶會靜默地停掉並記錄前一顆,那種靜默結算正是領取鎖
那條規則當初要擋的,不能在這裡反過來製造它。錶本來就沒在跑不算失敗,這一步多半排在
回報之前,報成失敗只會讓人以為前面那件事沒做成而重跑一次。
time-log 補登議題建立之前那一段——讀齊輸入、逐項詢問、組出議題內容,往往是整個 plan
最耗時的部分,而那時候議題還不存在,沒有標的可起錶。長度由腳本自己算(議題的建立時間
減掉 --since),交給 agent 做減法等於讓兩邊的時鐘各算一次,而算錯了報表上看不出來。
**不設時間上限、照實補登**:中途去開會的兩小時會一起算進去,換來這個流程不必為此
多長一題出來問使用者。
補登不是冪等的動作,所以兩種情況跳過不補:錶已經跑在這顆議題上(補登排在起錶之前,
錶在跑就代表這一段補過了),以及這顆議題上已經有工時(整個流程跑完過一次)。工時記
重複比記不到更難在報表上被發現,所以判斷偏向不補。
時間追蹤在現有環境下可能是關著的,真實路徑跑不起來;兩支都靠 --dry-run 與 stub
server 測,共 27 條。
議題 #57
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 19:32:20 +08:00
jiantw83 and Claude Opus 5
5638593c4c
refactor(lib): 停錶與議題工時清單下沉到 lib
...
停錶原本只長在 pr-create 裡,而規劃與分析接下來也要停自己的錶(議題 #57)。
那段容錯邏輯——「錶沒在跑」的狀態碼隨站台版本而異,認的是狀態碼在 409/500
這一組**且**訊息說的是碼錶——各寫一份遲早會在某一邊漏掉一種狀態碼,而它漏掉的
症狀正是「事情做完了卻回報失敗」。
順手把簽章改成與同伴一致的 (login, repo, index):fetchIssue、listIssueTimes、
stopwatchOnIssue 都這樣收,只有它收一條手組的路徑字串。
新增 listIssueTimes 供補登判斷「這顆議題上已經有工時了嗎」。它在議題讀得到卻
404 時報 TIME_TRACKER_OFF,與四層前置檢查第四層說同一句話——試跑不跑前置檢查,
那句話得由它自己說,否則試跑看到的是一句看不懂的 404。
議題 #57
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 19:32:01 +08:00
jiantw83 and Claude Opus 5
a279591232
fix(sdlc-fix): 需求議題本來就沒有未整併留言時照樣列出工作包
...
原本只寫「未處理數大於 0 就先整併」,零則的入口從沒明寫,讀起來會變成
「沒有留言要處理就到此為止」,正好與「無未整併留言時要列出工作包」相反。
另把交棒的另一端補在 sdlc-feat 的輸入上——機制只有一邊寫得出來,
另一邊就只是散文承諾。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 18:21:36 +08:00
jiantw83 and Claude Opus 5
03ce382220
refactor(issue-body): 工作包的歸屬判準收成一個函式
...
wp-extract 與 wp-list 都在問「這顆工作包掛在哪顆需求底下」。規則寫兩份,
某天只會有一邊被改到,而分岔的樣子是「清單裡看得到、抽取卻說不是」。
順手把測試裡兩種取段落的寫法統一,並刪掉沒有人傳過的參數。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 18:19:33 +08:00
jiantw83 and Claude Opus 5
a5f36ed14c
feat(sdlc-fix): 輸入放寬為 PR 編號或議題編號
...
議題不自己重做一遍實作流程,而是交棒給 /sdlc-feat:工作包議題直接交棒,
需求議題先讓 /sdlc-sync 整併決策留言,剩下確實要改碼的才列出底下的工作包
讓使用者挑一顆。交棒複用 sdlc-sync 已經定好的接回機制,使用者不必重打指令。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 18:16:23 +08:00
jiantw83 and Claude Opus 5
09d880e6f2
feat(wp-list): 列出一顆需求議題底下的工作包
...
歸屬判準沿用工作包抽取那一套(關聯段落的「需求議題:#N」),不另發明一套;
PR 的描述也有那一行,所以先擋掉 PR。清單逐頁讀完,讀不完寧可報錯——
使用者會從缺了幾顆的清單裡挑,而且看不出缺的是哪幾顆。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 18:16:23 +08:00
jiantw83 and Claude Opus 5
9ed719a4d7
fix(pr-create): 錶沒在跑時 409 與 500 都不算失敗
...
Gitea 回「cannot stop a non-existent stopwatch」時用的狀態碼隨站台而異:這台回 409,
而腳本只認 500。結果是 PR 已經開出去了,卻以 exit 1 與 HTTP_ERROR 收場——照它自己
寫下的理由,那會讓人以為 PR 沒開成而重跑一次。三次重現(議題 #41、#50、#42)。
認的是「狀態碼在 409/500 這一組 **且** 訊息說的是碼錶」:只看訊息會把真的伺服器錯誤
一起吞掉,只看狀態碼會把別的衝突也當成沒錶。兩種狀態碼各一條測試,另加一條
「訊息對不上的 409 照常拋出」。
README 與 AGENTS.md 的「六個流程正本尚未到齊」也一併改掉——六份都在了,那句話會讓
使用者以為裝了也沒指令可用,在 AGENTS.md 裡還會誤導下一個 agent。並補一條測試把說法
與 prompts/ 的實際份數釘在一起,免得下次又走鐘。
議題 #54
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:46:43 +08:00
jiantw83 and Claude Opus 5
66064a751e
fix(lib): 補回搬家時掉了的 rmSync,並讓 origin 檢查只有一份
...
review 抓到一個沉默的失效:rollback 最後那一手「git 清不乾淨時把目錄本身也清掉」呼叫
rmSync,但把這段從 branch-prep 搬進 lib 時,import 留在了原處。那一行落在自己的
try/catch 裡,所以 ReferenceError 被吞掉——註解承諾的事從來沒發生過,而且測試全綠。
下一次重跑會撞上 WORKTREE_PATH_TAKEN,人看到的是一句與真正原因無關的錯誤。
順手收掉兩支腳本各寫一次的 origin 檢查(只有「為什麼需要它」那一句不同,由呼叫端給),
並把 lib 檔頭「負責六件事」改成七件——工作樹的一生現在整個住在這裡。
正本三處跟著改:
- 查現況那一行補上 --dry-run。pr-watch 在 PR 已終止時會順手清掉工作樹,而「我想看一下
留言」不該把清理順便做掉——清不清理是使用者的決定。
- 拿掉「使用者堅持要繼續就繼續」:它與同一份正本的邊界(已合併或關閉時不繼續處理留言)
直接矛盾,而在一棵該被清掉的工作樹上改東西,那些改動不會進到任何 PR 裡。
- worktree-ensure 的指令補上 --path:目標專案多半不是當前目錄,而「不必先手動 cd」
正是這一步要解決的問題。
議題 #42
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:33:44 +08:00
jiantw83 and Claude Opus 5
0dd8d7b7bb
docs(sdlc-fix): 第一步先看 PR 現況,再定位工作樹
...
補上 #14 沒有的工作樹概念:它預設在當前目錄處理留言,而留言要改的程式碼在那顆工作包
自己的工作樹裡。新的第一步先問 pr-watch 現況——PR 已經合併或關閉就停下來,在一棵該被
清掉的工作樹上處理留言是白做工——再用 worktree-ensure 定位,不在就重建。
邊界同時擋住兩件事:不回主工作區處理留言(它可能停在別的分支上),以及推導路徑上有
別的東西時不自己刪。原有的五個步驟整體往後移一號,內容不變。
議題 #42
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:33:44 +08:00
jiantw83 and Claude Opus 5
2562555058
feat(worktree-ensure): 定位工作包的工作樹,不在就重建
...
處理 PR 留言的人不必先手動 cd 到正確的目錄:路徑由 owner/repo/分支名 純函式推導,
問這一支就知道該在哪裡動手。
「不在就重建」是常態不是防禦性程式設計。進度完全不寫在本機——換一台機器或換一個
agent 接手時,工作樹本來就不存在,而重建的成本就是一次 git worktree add。
兩種情況明確中止而不是硬幹:分支在本機與遠端都不見時報 BRANCH_NOT_FOUND(憑空長一棵
空的工作樹只會讓人以為進度還在),推導出的路徑上是別的東西時報 WORKTREE_PATH_TAKEN。
議題 #42
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:33:44 +08:00
jiantw83 and Claude Opus 5
7196385c5e
refactor(branch-prep): 建立工作樹的「算」與「做」收進 lib
...
重建工作樹(下一個 commit 的 worktree-ensure)要走的是與開工時完全同一套:一樣先
git fetch 更新遠端引用,一樣不設 upstream,分支已經存在就接上去而不是長一棵空的。
兩邊各寫一份,遲早會在「起點取自哪裡」這種地方分岔——而那種分岔要等到有人的進度
不見了才會被發現。
planWorktree 多接受一種用法:不給來源分支就是「重建既有分支的工作樹」,沒有「從來源
長一支新的」那條路,走到那裡就是 BRANCH_NOT_FOUND。branch-prep 的行為完全不變。
議題 #42
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:33:44 +08:00
jiantw83 and Claude Opus 5
ceb723c63e
test(流程正本): 新增 sdlc-sync,並讓另外兩份正本整併完自動接回
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:11 +00:00
jiantw83 and Claude Opus 5
f85fabb758
feat(流程正本): 新增 sdlc-sync,並讓另外兩份正本整併完自動接回
...
挑出哪些留言真的是決策、寫回去之前讓使用者點頭、略過的保持未標記——三件都只有正本做得到。
analyze 與 feat 原本寫「建議先執行 /sdlc-sync,再回來」,那等於要使用者重打指令。改成直接走
sync 的流程、做完自動接回,並在接回前重新抽取一次——接著用舊的那一份做事,這一整段就白做了。
略過的留言會讓未處理留言數停在大於 0,於是 analyze 與 feat 每次都會再停一次。這是驗收標準
本身的兩條放在一起的結果,正本把它講明白,讓使用者分得出「沒整併乾淨」與「我選了略過」。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:10 +00:00
jiantw83 and Claude Opus 5
3ad08be14f
test(comments-merge): 把留言裡的決策整併回議題描述並標記
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:10 +00:00
jiantw83 and Claude Opus 5
16f93f6e8b
feat(comments-merge): 把留言裡的決策整併回議題描述並標記
...
判斷「哪幾則有決策、該併進哪一段」是讀得懂內容的人的事;這一支只負責把結果安全地寫回去。
先寫描述再標記,描述寫失敗就不標記:反過來的話,那幾則已經被標成處理過,再也不會被提出來。
--merged 只收真的併進去的那幾則,略過的保持未標記。標記之前先核對那幾則確實在這顆議題上——
打錯 id 的 reaction 會落在別顆議題的留言上,而那幾乎不會有人發現。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:09 +00:00
jiantw83 and Claude Opus 5
5ef25d3ee9
test(抽取契約): 未整併的判定改成只認自己打的 +1
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:08 +00:00
jiantw83 and Claude Opus 5
c1ab712afd
feat(議題解析): 換掉一個段落的內容,標題與其餘段落一字不動
...
整併留言裡的決策時用它。不整份重寫的理由跟 upsertLineInSection 一樣,只是代價更大:
重寫會把別人在其他段落的編輯一起蓋掉,而議題的編輯紀錄沒有人會去比對。
三件事照著同檔既有的規矩做:圍欄裡的假標題不算段落;同名標題出現不只一次時交回 ambiguous
而不賭第一個(蓋掉的是一整段,猜錯的代價比 tickLine 更高);換行沿用 body 原本的那一種,
CRLF 的 body 裡混進 LF 會讓抽取契約交出的 raw 對不上原文,之後就勾不動那幾行。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:08 +00:00
jiantw83 and Claude Opus 5
3b7e3bdfc7
test(pr-comments): 議題也讀得了,不再先打 PR 端點
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:07 +00:00
jiantw83 and Claude Opus 5
051fe0ded4
fix(pr-comments): 議題也讀得了,不再先打 PR 端點
...
每個 PR 都是議題,議題不一定是 PR——原本的註解把這句話講反了,程式也照著反過來寫:
先打 /pulls/{index},對純議題回 404,於是 /sdlc-sync 在讀到第一則留言之前就斷了。
改成先讀 /issues/{index}(兩種都有),看它有沒有 pull_request 才決定要不要去翻 review。
輸出加上「類型」讓下游知道拿到的是議題還是 PR。
讀取的共用結構沿用 pr-threads(#48 為了讓 pr-watch 數同一件事而抽出來的):readGeneral
改名 readGeneralComments 並導出,純議題只叫它;pr-threads 自己那份 markedByMe 拿掉,
改用 lib 的 mergedByMe。#48 的檔頭擔心「規則寫兩份遲早會各自演化」,現在三處共用一份。
試跑改印 commonRequests:pr-comments 收得下兩種輸入,而試跑階段還沒讀過議題、不知道是
哪一種。與其假設是 PR 而列出五個(對純議題有三個根本不會發),不如只列一定會發的。
pr-watch 的輸入一定是 PR,繼續用 plannedRequests。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:06 +00:00
jiantw83 and Claude Opus 5
b263915afc
test(pr-create): 讀檔與列留言收進 lib,並讓已整併只認自己打的 +1
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:05 +00:00
jiantw83 and Claude Opus 5
ee474d0439
refactor(lib): 讀檔與列留言收進 lib,並讓已整併只認自己打的 +1
...
「讀一個 --xxx-file 或直接失敗」原本在四支腳本各寫一份,錯誤碼還有三種拼法
(BODY_FILE_NOT_FOUND/BODY_FILE_MISSING/CONTENT_FILE_NOT_FOUND)。同一種情況要有同一個
碼,呼叫端才分辨得出是哪一步壞了。留言分頁的那段咒語也是第三份,一併收成 listIssueComments。
countUnmergedComments 原本接受任何人的 +1,而讀留言那邊只認自己的——兩端對「已整併」的
定義不一致。後果是隊友對決策留言按個讚,未處理留言數就掉到 0,analyze 與 feat 再也不提示,
那則決策永遠不會被收進描述。統一成只認自己打的:別人按讚是「我同意」,不是「已經收進去了」。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 09:19:05 +00:00
jiantw83 and Claude Opus 5
695694b3fd
fix(pr-watch): 來源分支被刪掉之後,改從 head.label 取分支名
...
PR 合併而來源分支被刪掉之後,Gitea 把 head.ref 換成 refs/pull/{編號}/head,分支名
退到 head.label。pr-watch 用的是 head.ref,於是推導出一條根本不存在的工作樹路徑,
回報「工作樹不在、沒事要做」——而合併正是唯一該清理工作樹的時機,等於自動清理在真實
情況下從來不會成立,而且靜悄悄的,沒有人會發現。
實測 #48(合併後)推導出 bb4f5127f641,真正的那一棵在 bd457620184e。
fork 來的 PR 的 label 是 owner:branch,只取分支那一段。兩邊都取不到時明確中止
(PULL_HEAD_MISSING),並指出手動出口怎麼指名那一棵,不拿空字串去推導路徑。
測試先前沒抓到,是因為假的 PR 一律照「開著的 PR」寫,head.ref 永遠是分支名。
補上「已合併且來源分支已刪」與 fork 兩種 head 的樣子。
議題 #50
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:16:44 +08:00
jiantw83 and Claude Opus 5
617a3eef72
fix(pr-watch): 試跑的預告與實跑的守門對齊,並說出路徑被佔住這件事
...
review 抓到一處落差:試跑判「終止且工作樹乾淨」就預告 git worktree remove,
但實跑多判一件事——那條路徑上的東西是不是真的一棵工作樹。別的 clone 在同一條路徑上
留下目錄時(路徑由 owner/repo/分支名 推導,不含本機 clone 的位置),試跑會預告一行
實跑必然拒絕的指令,而回報裡完全看不出原因。
判斷收進 lib 的 inspectWorktree,由它直接給出 reason(missing/foreign/dirty/
removable):試跑與實跑、自動與手動四條路徑從此擋在同一個判斷上,worktree-remove 裡
那份重算的副本也跟著刪掉。回報多一個「是工作樹」欄位,手動出口的 NOT_A_WORKTREE
不再是使用者第一次聽到這件事。
順帶兩件同源的修正:
- 「是不是工作樹」改認 .git 為**檔案**。獨立 clone 的 .git 是目錄,先前會被當成工作樹,
然後在 git worktree remove 那一步炸出一句原始錯誤。
- 兩支腳本的 --dry-run 請求預告收進 pr-threads 的 plannedRequests,並補上 pr-watch
先前漏掉的那句說明(逐則 reaction 與逐個 review 的行內留言事前列不完)。預告與實際
發出的請求分開寫,加一個端點就會有一邊忘了改。
- PR 讀不到 head 分支時給出 PULL_HEAD_MISSING,而不是拿空字串去推導一條路徑。
議題 #41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 17:06:07 +08:00
jiantw83 and Claude Opus 5
32edd65962
docs(sdlc-feat): 第三段收尾指向 pr-watch 與手動清理
...
PR 開出去之後流程就斷在那裡,使用者不會知道有東西可以查現況、也不會知道工作樹會被
自動清掉。收尾補一步,把兩支腳本講給使用者聽,並明講「多久跑一次由他自己排」。
邊界同時擋住兩件事:agent 不自己反覆跑 pr-watch,也不因為它建議了 run-sdlc-fix 就
自己去跑 /sdlc-fix——流程只由使用者明確叫用。
議題 #41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:59:30 +08:00
jiantw83 and Claude Opus 5
d331bef1ed
feat(pr-watch): 回報 PR 現況,並在它結束時清掉工作樹
...
開發者要能隨時問一句「這個 PR 現在怎麼樣、我還有什麼要做」。回報 PR 狀態、還有幾則
留言沒處理、工作樹在哪、裡面有沒有沒提交的東西,以及固定列舉值的 suggestedAction
——用列舉值而不是一段文字,呼叫端才能程式化判斷。
一次性、無狀態:不做變化偵測。已處理的判定基準是 Gitea 上的 +1 與 resolve,那個狀態
不在本機記憶裡,所以一份現況快照就足以回答「還有沒有事要做」,不必跟上次比較,也就
不必留任何游標或狀態檔。不做常駐程序也不做 daemon,排程交給呼叫端。
只通知,不動手:偵測到未處理留言只給建議,不自動執行 /sdlc-fix——流程不該被模型自動
觸發,而 /sdlc-fix 要求「不確定時詢問使用者」,非互動模式下那個詢問無處可去。
四種狀態的處置不一致,所以表格驅動測:merged 與 closed 終止並清理;open 繼續監看;
draft 繼續監看且**不清理**——被退回草稿代表還要繼續改,這時候那棵工作樹更需要留著。
議題 #41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:59:30 +08:00
jiantw83 and Claude Opus 5
9e049580bb
feat(worktree-remove): 手動清掉一棵工作樹,絕不 --force
...
pr-watch 會在 PR 合併或關閉時自動清理,但永遠不會被合併也不會被關閉的 PR 沒有出口
——沒有這一支,那些工作樹只能靠使用者自己記得去刪。兩條路共用 lib 的 removeWorktree,
不互相開子行程:守門的規則只有一份,自動的那條與手動的這條不該長出兩種行為。
**絕不 --force。** 清理會被自動執行,而自動執行的東西只能做可逆的事:工作樹重建得回來,
被刪掉的未提交變更救不回來。所以有東西沒提交就中止並報出路徑與檔名。只移除工作樹,
本機分支與遠端分支都留著。
輸入是 owner/repo 與分支名而不是一條路徑:要刪哪一棵由「哪顆工作包」決定,
使用者不必自己去記 12 碼的雜湊目錄名。
順手修掉一個測試抓出來的解析錯誤:git status --porcelain 的第一行前導空白會被 runGit
的 trim 修掉,固定切前三個字元會讓已修改檔案的檔名少一個字(README.md 變成 EADME.md),
人照著訊息去找會找不到。改成認「狀態欄一到兩個字元 + 空白」。
議題 #41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:59:30 +08:00
jiantw83 and Claude Opus 5
43764f457e
refactor(pr-comments): 三類留言的讀取收進 pr-threads
...
pr-watch 要數「還有幾則沒處理」,數的是與 pr-comments 完全同一件事:三類留言分散在
三個端點,一般留言與總評看自己打的 +1、行內留言看有沒有被 resolve,而總評的 reaction
掛在它的 issue comment id 上。這套規則寫兩份,遲早會一邊認自己的 +1、另一邊認任何人的
——而那個差異要等到有留言被靜靜跳過才會被發現。
pr-comments 只留輸入輸出,行為不變。
議題 #41
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:59:30 +08:00
jiantw83
a1bbd78cb4
test(sdlc-fix-assets): 新增 sdlc-fix 流程正本
...
分類必改/建議、不確定時停下來問、最後那則摘要——這三件腳本擋不住,只有正本做得到。
標不了的那幾則要在摘要裡單獨點出來,否則 reviewer 掃 reaction 與 resolve 時會以為它們
被跳過了。留言指向的程式碼已被改掉時不要硬試,列進「無法處理」讓使用者自己回。
2026-09-17 08:46:35 +00:00
jiantw83
6453c78fd1
feat(sdlc-fix): 新增 sdlc-fix 流程正本
...
分類必改/建議、不確定時停下來問、最後那則摘要——這三件腳本擋不住,只有正本做得到。
標不了的那幾則要在摘要裡單獨點出來,否則 reviewer 掃 reaction 與 resolve 時會以為它們
被跳過了。留言指向的程式碼已被改掉時不要硬試,列進「無法處理」讓使用者自己回。
2026-09-17 08:46:35 +00:00
jiantw83
8d9bcd65e3
test(pr-reply): 回覆一則 PR 留言並標記已處理
...
「回在原本那一串底下」對三類是三件不同的事。行內留言沒有「回覆某一則」的端點,
把新留言指向同一個檔案與同一行,Gitea 才會把它排在原留言底下——位置是串的識別。位置不由
呼叫端給而是拿 id 查出來:手抄行號是這一段最容易錯的地方。回覆還帶上原留言的 commit_id,
否則 PR 之後又推了新 commit 時,同一個行號指的是別的程式碼。
標記排在回覆之後,回覆沒成功就不標記:沒回就標記等於謊稱處理過。
輸出三類同形:用不到的欄位填 null 而不是讓它消失,下游不必為了少一欄多寫一種分支。
2026-09-17 08:46:34 +00:00
jiantw83
759b53adeb
feat(pr-reply): 回覆一則 PR 留言並標記已處理
...
「回在原本那一串底下」對三類是三件不同的事。行內留言沒有「回覆某一則」的端點,
把新留言指向同一個檔案與同一行,Gitea 才會把它排在原留言底下——位置是串的識別。位置不由
呼叫端給而是拿 id 查出來:手抄行號是這一段最容易錯的地方。回覆還帶上原留言的 commit_id,
否則 PR 之後又推了新 commit 時,同一個行號指的是別的程式碼。
標記排在回覆之後,回覆沒成功就不標記:沒回就標記等於謊稱處理過。
輸出三類同形:用不到的欄位填 null 而不是讓它消失,下游不必為了少一欄多寫一種分支。
2026-09-17 08:46:34 +00:00
jiantw83
de1fd08efa
test(pr-comments): 讀 PR 上的三類留言
...
一般留言、review 總評、行內留言分散在三個端點,漏掉任何一類就會有意見沒被處理——
而那正是 /sdlc-fix 存在的理由。只讀不寫,必改/建議的分類由讀到內容的人判斷。
三類的「已處理」機制不同:一般留言與總評看自己打的 +1,行內留言看有沒有被 resolve。
總評的 reaction 掛在它在 issue comment 表裡那一份的 id 上,由 timeline 對應得出來;
拿 review 自己的 id 去打會 404,兩個 id 不同命名空間。
reaction 要是自己打的才算已處理:reviewer 對留言按讚是「我同意」,不是「這則處理過了」,
當成已處理會讓那一則被靜靜跳過。
行內留言的位置分兩側:新檔那側在 position,被刪掉的那行在 original_position,Gitea 只填
其中一個。輸出把兩者收斂成「行」與「側」,回覆時才知道該送哪個欄位。也帶出 commit,
讓回覆落在原留言的那個 commit 上。
三種清單都逐頁讀完:這個站台的預設頁大小是 30,沒分頁的話第 31 個 review 之後整批消失。
2026-09-17 08:46:33 +00:00
jiantw83
cc29b5bc21
feat(pr-comments): 讀 PR 上的三類留言
...
一般留言、review 總評、行內留言分散在三個端點,漏掉任何一類就會有意見沒被處理——
而那正是 /sdlc-fix 存在的理由。只讀不寫,必改/建議的分類由讀到內容的人判斷。
三類的「已處理」機制不同:一般留言與總評看自己打的 +1,行內留言看有沒有被 resolve。
總評的 reaction 掛在它在 issue comment 表裡那一份的 id 上,由 timeline 對應得出來;
拿 review 自己的 id 去打會 404,兩個 id 不同命名空間。
reaction 要是自己打的才算已處理:reviewer 對留言按讚是「我同意」,不是「這則處理過了」,
當成已處理會讓那一則被靜靜跳過。
行內留言的位置分兩側:新檔那側在 position,被刪掉的那行在 original_position,Gitea 只填
其中一個。輸出把兩者收斂成「行」與「側」,回覆時才知道該送哪個欄位。也帶出 commit,
讓回覆落在原留言的那個 commit 上。
三種清單都逐頁讀完:這個站台的預設頁大小是 30,沒分頁的話第 31 個 review 之後整批消失。
2026-09-17 08:46:33 +00:00
jiantw83 and Claude Opus 5
d3ce364727
docs(sdlc-feat): description 跟著改成「備妥工作樹、起錶」
...
轉接檔的一行說明是從這裡取的,順序寫錯會讓人以為錶還是在領取那一步起。
議題 #40
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:30:00 +08:00
jiantw83 and Claude Opus 5
0154cf59d4
fix(claim): 被碼錶擋下時明說停錶不會動到既有的工作樹
...
議題 #38 的使用者故事第 30 條:使用者常以為停錶等於放棄那顆工作包,於是寧可
不停——工時就記到別顆議題去了。碼錶只管時間、工作樹只管檔案,兩者互不相干,
這件事要在擋下來的當下就講,不能指望使用者自己推論。
領取與起錶會撞到同一個擋路理由,訊息收進 lib 只寫一份。順手收掉 review 指出的
三處:planWorktree 沒用到的 repo 參數、與 path.resolve 同名而誤導的區域函式、
以及只有 lib 自己用得到卻對外 export 的兩支路徑函式。
回滾補上最後一道:git 清不掉時把目錄本身也刪掉。那條路徑在這次執行之前不存在
(不存在正是建立的前提),裡面不可能有使用者的東西,而留著它下一次重跑會直接
撞上 WORKTREE_PATH_TAKEN——一次失敗的建立不該讓人從此開不了工。
議題 #40
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:29:12 +08:00
jiantw83 and Claude Opus 5
4583a5f210
docs(sdlc-feat): 第一段改成領取、備妥工作樹、最後起錶
...
正本跟著實作走:分支那一步改成工作樹,新增起錶那一步排在它後面,
並把三處「不要自己決定」寫明——來源分支不存在時不自己換一支、路徑被佔住時
不自己刪、工作樹建不起來時不退回原地切分支。工作區不乾淨的處置整段拿掉:
工作樹本來就是為了讓未提交的變更不再擋路。
AGENTS.md 補上兩條邊界。git worktree add 一定會在目標 repo 的 .git/worktrees/
底下寫中繼資料,這是 git 的機制,無法避免——「不改目標專案」指的是專案的內容檔,
把這件事明說,免得下一個人以為實作違規。
議題 #40
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:29:12 +08:00
jiantw83 and Claude Opus 5
74b4ca130e
feat(timer): 碼錶移出領取,等工作樹建成之後才起
...
原本的順序是「放行 → 設 assignee 與標籤 → 起錶 → 處理分支」,而工作樹建立
失敗會中止整個領取——錶已經起了才失敗,使用者會被計一段什麼都沒做的時間,
而工時要準正是工時報表的立足點。
claim 只留領取鎖的兩件事(assignee 與標籤),起錶交給新的 timer.js,由流程
正本排在 branch-prep 之後。timer 已經跑在這顆議題上時什麼都不做:中斷後重跑
是它最常見的處境,重新起錶會把已經累積的時間切成兩段;跑在別顆上則照舊擋下,
不代勞停錶。
三支腳本讀碼錶的那段各留一份,趁這次收進 lib。
議題 #40
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:27:43 +08:00
jiantw83 and Claude Opus 5
5b740c236b
feat(branch-prep): 一律在獨立的工作樹上開工,不在原地切換分支
...
同一份 clone 上同時持有多顆工作包時,原地切分支有三種損耗,一種比一種難查:
未提交的變更擋路、建置產物跨分支混淆,以及 agent 讀到不屬於它那顆工作包的
程式碼——agent 是非同步的,它可能在分支已經被切走之後才去讀檔,而且不會察覺,
產出看起來完全合理,只是接錯了上下文。前兩種人會當場發現,第三種不會,
所以工作樹一律建立,不是「有衝突才用」。
建不起來就中止,不退回原地切分支:靜默降級會讓使用者以為自己在隔離環境裡,
其實在原地改。
分支與工作樹合併為一個原子動作(fetch 後一次 worktree add),並補上回滾——
git 在 worktree add 失敗時仍會把分支留下來,那是最難查的半成品:下一次重跑
會走到「目標分支已存在」那條路,起點從此不再是遠端的來源分支。
起點一律取自 origin/{來源分支},遠端沒有就中止,不退回本機同名分支;
本機分支可能落後好幾天,而這件事從輸出上完全看不出來。原「來源分支在遠端
已存在時 pull 而非重建」那條,用更強的方式達成同一個目的:根本不碰本機分支,
就沒有覆蓋他人進度的可能。
不設 upstream:此刻遠端還沒有這個新分支,--track 會把 upstream 指到來源分支,
之後 git pull 會把來源分支的提交拉進來。留給第一次 push -u 自然建立。
路徑由 owner/repo/分支名 正規化後取雜湊推導(lib 的 worktreePath),不查表、
不寫狀態檔,換機器算出來一樣。取雜湊而不是把斜線攤平成 -,是因為攤平會讓
feat/a-b/main 與 feat/a/b/main 撞成同一個目錄,而現行的分支命名規則恰好讓
這種形狀有機會出現。
議題 #40
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 16:27:43 +08:00
jiantw83
997d4ec280
docs(sdlc-feat): 第三段補上兩個 repo 的區別與重跑的行為
...
說明 --repo 與 --issue-repo 的差別、--base 要明講不讓腳本猜、重跑不會開出第二顆 PR,
以及提交中途失敗時不要自己回捲歷史。
2026-09-17 08:23:28 +00:00
jiantw83
013947630f
test(sdlc-feat-assets): 第三段補上兩個 repo 的區別與重跑的行為
...
說明 --repo 與 --issue-repo 的差別、--base 要明講不讓腳本猜、重跑不會開出第二顆 PR,
以及提交中途失敗時不要自己回捲歷史。
2026-09-17 08:23:28 +00:00
jiantw83
da5b670f29
test(script-contract): 讓 --key=value 的值可以本身以 -- 開頭
...
commit 訊息與 PR 描述裡出現 --flag 是常態,而原本的檢查對兩種寫法一視同仁:值只要以
-- 開頭就報「需要一個值」。空格分隔的寫法確實分不出「值」與「打錯的 flag」,但等號寫法
沒有這個歧義,不該一起擋掉。
這是實際撞到的:拿 commit-split 提交它自己時,--body 的內容第一行就是「--repo 與
--issue-repo 的差別」,整個 commit 因此做不出來。空格寫法的錯誤訊息現在會指路到等號寫法。
2026-09-17 08:23:27 +00:00
jiantw83
1c6e7f85bb
fix(lib): 讓 --key=value 的值可以本身以 -- 開頭
...
commit 訊息與 PR 描述裡出現 --flag 是常態,而原本的檢查對兩種寫法一視同仁:值只要以
-- 開頭就報「需要一個值」。空格分隔的寫法確實分不出「值」與「打錯的 flag」,但等號寫法
沒有這個歧義,不該一起擋掉。
這是實際撞到的:拿 commit-split 提交它自己時,--body 的內容第一行就是「--repo 與
--issue-repo 的差別」,整個 commit 因此做不出來。空格寫法的錯誤訊息現在會指路到等號寫法。
2026-09-17 08:23:27 +00:00
jiantw83
95e6026950
refactor(lib): 開啟目標專案 git repo 的那幾行收進 lib
...
「路徑不是 repo 就報 NOT_A_GIT_REPO」加上「把 cwd 綁進 runGit」原本在 branch-prep 與
commit-split 各寫一份。錯誤碼要一致,而這件事寫第三遍就該收起來了。
2026-09-17 08:23:26 +00:00
jiantw83
fbc80f286f
test(pr-create): 錶停在議題的 repo,並讓重跑不會開出第二顆 PR
...
claim 在工作包議題上起錶,而 PR 開在目標專案上——議題在需求的 repo,程式碼在 repos 列的
那幾個,兩者常常不是同一個。先前用同一個 --repo 同時指 PR 與停錶,停到的會是別人的議題
(或 404 而在 PR 已建立之後才拋錯),而自己的錶還在跑。新增 --issue-repo,預設與 --repo 相同。
--index 與 --base 改為必填:停錶是這一步的一部分,忘了給會讓工時算不準;而目標專案的開發
分支可能叫 master、main 或 develop,猜錯會開到不存在的 base。
重跑先查同一個 head 有沒有開著的 PR,有就回傳它並把 created 設為 false,然後照樣停錶——
那一步可能正是上次中斷的地方。先前重跑會撞上 Gitea 的 422,而那個錯誤看不出 PR 其實已經開好。
停錶的 500 改為只在訊息確實提到 stopwatch 時才視為「本來就沒在跑」,免得把真的伺服器錯誤
吞掉;未經證實的 409 那一支拿掉。測試結果的空話檢查改成整段每一行都是空話才擋,段落也改用
行首標題切,描述裡引用到「## 測試結果」這幾個字不會再讓檢查看錯地方。
2026-09-17 08:23:25 +00:00
jiantw83
30297cc49a
fix(pr-create): 錶停在議題的 repo,並讓重跑不會開出第二顆 PR
...
claim 在工作包議題上起錶,而 PR 開在目標專案上——議題在需求的 repo,程式碼在 repos 列的
那幾個,兩者常常不是同一個。先前用同一個 --repo 同時指 PR 與停錶,停到的會是別人的議題
(或 404 而在 PR 已建立之後才拋錯),而自己的錶還在跑。新增 --issue-repo,預設與 --repo 相同。
--index 與 --base 改為必填:停錶是這一步的一部分,忘了給會讓工時算不準;而目標專案的開發
分支可能叫 master、main 或 develop,猜錯會開到不存在的 base。
重跑先查同一個 head 有沒有開著的 PR,有就回傳它並把 created 設為 false,然後照樣停錶——
那一步可能正是上次中斷的地方。先前重跑會撞上 Gitea 的 422,而那個錯誤看不出 PR 其實已經開好。
停錶的 500 改為只在訊息確實提到 stopwatch 時才視為「本來就沒在跑」,免得把真的伺服器錯誤
吞掉;未經證實的 409 那一支拿掉。測試結果的空話檢查改成整段每一行都是空話才擋,段落也改用
行首標題切,描述裡引用到「## 測試結果」這幾個字不會再讓檢查看錯地方。
2026-09-17 08:23:25 +00:00
jiantw83
8a6145ea14
test(commit-split): 改名時別漏掉舊檔的刪除,失敗時說出做到哪裡
...
git diff --name-only 預設偵測改名,只印出目的地那一個路徑。來源的刪除因此被漏掉——
留在 index 裡沒被提交,而腳本還回報成功,要等下一次跑才會發現工作區不乾淨。加 --no-renames。
某一批提交失敗時,錯誤現在會列出前面已經建立的那幾顆 commit。不回捲它們:那會動到使用者的
歷史,而那幾顆本身是好的;但一定要說出做到哪裡,否則重跑前得自己去翻 git log。
類型對照表補上目標專案常見的測試擺法:tests/、spec/、__tests__/,以及放在被測檔案旁邊的
user.test.js。先前只認 test/,目標專案的測試會被併進 feat 那一批。
2026-09-17 08:23:24 +00:00
jiantw83
f99adab245
fix(commit-split): 改名時別漏掉舊檔的刪除,失敗時說出做到哪裡
...
git diff --name-only 預設偵測改名,只印出目的地那一個路徑。來源的刪除因此被漏掉——
留在 index 裡沒被提交,而腳本還回報成功,要等下一次跑才會發現工作區不乾淨。加 --no-renames。
某一批提交失敗時,錯誤現在會列出前面已經建立的那幾顆 commit。不回捲它們:那會動到使用者的
歷史,而那幾顆本身是好的;但一定要說出做到哪裡,否則重跑前得自己去翻 git log。
類型對照表補上目標專案常見的測試擺法:tests/、spec/、__tests__/,以及放在被測檔案旁邊的
user.test.js。先前只認 test/,目標專案的測試會被併進 feat 那一批。
2026-09-17 08:23:23 +00:00
jiantw83
513dad7f6a
test(sdlc-feat-assets): 加入第三段「提交與開立 PR」
...
PR 描述的八個段落與順序寫在正本裡,由 pr-create 擋;正本負責的是腳本擋不住的事:
測試結果要貼實際輸出而不是改寫成一句話、被擋下來時補真的內容而不是為了通過而拼湊、
以及跨兩個功能時用 --files 分兩次跑。
先開 PR 再停錶的理由也寫進去了:工時要記在真的有做事的那段時間上。
2026-09-17 08:23:23 +00:00
jiantw83
0b728ca270
feat(sdlc-feat): 加入第三段「提交與開立 PR」
...
PR 描述的八個段落與順序寫在正本裡,由 pr-create 擋;正本負責的是腳本擋不住的事:
測試結果要貼實際輸出而不是改寫成一句話、被擋下來時補真的內容而不是為了通過而拼湊、
以及跨兩個功能時用 --files 分兩次跑。
先開 PR 再停錶的理由也寫進去了:工時要記在真的有做事的那段時間上。
2026-09-17 08:23:22 +00:00
jiantw83
958b1f85e8
test(pr-create): 開立 PR 並停錶
...
標題等同分支名:reviewer 在列表上看到的就是分支,兩者對不上會找錯 PR。
描述的段落固定且順序固定,缺一段或順序不對就擋下,不自動補——補出來的段落是編的,
而 reviewer 會把它當成真的。
「測試結果」另外驗一次它不是空話。那一段是 reviewer 唯一能判斷「這東西真的跑過嗎」的
依據,寫「已測試通過」等於沒寫。判斷刻意很窄,只擋「整段只有一行,而那一行是已知的
偷懶寫法」——這一關要擋的是明顯沒跑過就交差,不是去評價別人的測試寫得夠不夠好。
沒有自動化測試時,寫得出可重現的手動驗證步驟就放行。
停錶排在 PR 開出去之後,而且只在 PR 真的建立了才停:工時要記在真的有做事的那段時間上。
錶本來就沒在跑不算失敗(Gitea 對此回 500)——PR 已經開出去了,把整件事報成失敗只會讓人
以為 PR 沒開成而重跑一次。
2026-09-17 08:23:22 +00:00
jiantw83
b5214ed0f1
feat(pr-create): 開立 PR 並停錶
...
標題等同分支名:reviewer 在列表上看到的就是分支,兩者對不上會找錯 PR。
描述的段落固定且順序固定,缺一段或順序不對就擋下,不自動補——補出來的段落是編的,
而 reviewer 會把它當成真的。
「測試結果」另外驗一次它不是空話。那一段是 reviewer 唯一能判斷「這東西真的跑過嗎」的
依據,寫「已測試通過」等於沒寫。判斷刻意很窄,只擋「整段只有一行,而那一行是已知的
偷懶寫法」——這一關要擋的是明顯沒跑過就交差,不是去評價別人的測試寫得夠不夠好。
沒有自動化測試時,寫得出可重現的手動驗證步驟就放行。
停錶排在 PR 開出去之後,而且只在 PR 真的建立了才停:工時要記在真的有做事的那段時間上。
錶本來就沒在跑不算失敗(Gitea 對此回 500)——PR 已經開出去了,把整件事報成失敗只會讓人
以為 PR 沒開成而重跑一次。
2026-09-17 08:23:21 +00:00
jiantw83
d6b44e0ba8
test(分批提交): 把變更依類型分批 commit
...
一個 commit 只裝一種類型:程式碼、測試、文件、雜項各自成批,reviewer 一次只看一件事,
日後 git log 也讀得懂。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。
類型多半看得出來——測試檔就是 test、README 就是 docs——但 scripts/ 底下的改動是新功能
還是修 bug,只有做的人知道,所以那一批由 --type 指定。這張對照表是純字串規則,表格驅動。
scope 單檔用檔名(claim.test.js 的 scope 是 claim,不是 claim.test),多檔用 --scope 的
功能名。描述要有中文:日後回顧時看得懂的是中文,而夾雜英文的專有名詞本來就該保留原文。
--files 讓一次變更橫跨兩個功能時能分兩次跑;--body 讓工具產出的歷史與本 repo 既有的
commit 一樣說明得出「為什麼」。
列變更檔案刻意不用 git status --porcelain:它的前兩欄是狀態碼,而 runGit 會 trim 掉
輸出的前導空白,未 staged 的修改會少掉檔名的第一個字元。
2026-09-17 08:23:21 +00:00
jiantw83
bb886457fd
feat(commit-split): 把變更依類型分批 commit
...
一個 commit 只裝一種類型:程式碼、測試、文件、雜項各自成批,reviewer 一次只看一件事,
日後 git log 也讀得懂。全部混成一顆「完成工作包」的巨大 commit,等於沒有歷史。
類型多半看得出來——測試檔就是 test、README 就是 docs——但 scripts/ 底下的改動是新功能
還是修 bug,只有做的人知道,所以那一批由 --type 指定。這張對照表是純字串規則,表格驅動。
scope 單檔用檔名(claim.test.js 的 scope 是 claim,不是 claim.test),多檔用 --scope 的
功能名。描述要有中文:日後回顧時看得懂的是中文,而夾雜英文的專有名詞本來就該保留原文。
--files 讓一次變更橫跨兩個功能時能分兩次跑;--body 讓工具產出的歷史與本 repo 既有的
commit 一樣說明得出「為什麼」。
列變更檔案刻意不用 git status --porcelain:它的前兩欄是狀態碼,而 runGit 會 trim 掉
輸出的前導空白,未 staged 的修改會少掉檔名的第一個字元。
2026-09-17 08:23:20 +00:00
jiantw83 and Claude Opus 5
8ea6a2a8b3
test(逐項實作): 覆蓋勾選的五種危險、兩份規則正本與正本第二段
...
勾選的測試全部繞著「會不會改錯行」打轉,五種都是 code review 抓出來的實際缺陷:
圍欄裡長得像 checkbox 的那一行不會被改到、不同段落的同一句話靠 --section 分得開、
大寫 [X] 重跑是 no-op、方框後沒有空白照樣勾得到、沒有方框的項目給的是指路的錯誤
而不是謊報已勾過。
另外釘住抽取端與勾選端的一致性:wp-extract 交得出來的每一種 raw,--tick 都要收得下。
patchOf 收進 helpers——它先前在三個測試檔裡各有一份一模一樣的定義。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:39:54 +00:00
jiantw83 and Claude Opus 5
a45e981c95
feat(流程正本): sdlc-feat 加入第二段「逐項實作」
...
一項一項做完並即時勾選,讓議題頁的進度條隨時反映真實狀態。
過程不打斷:二十項待辦不按二十次同意,只印進度;也不為了勾選留留言——勾選改的是 body,
進度條自己會動,逐項留言會把議題洗版,reviewer 得從一堆「已完成第 N 項」裡找真正的討論。
真正該停下來問的只有三種,列出來了。
規則正本指名讀取,不在這裡複述——抄過來就會有兩份各自演化的規則。
中斷後重跑從 Gitea 的勾選狀態接續,不看任何本機檔案;重複勾選是安靜的 no-op,
所以不確定某一項有沒有勾到時直接再勾一次即可,不必先查。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:39:48 +00:00
jiantw83 and Claude Opus 5
6e0fa92e73
feat(規則正本): 新增實作規範與註解格式對照表
...
兩份規則只存在於本 plugin 裡,由流程正本指名讀取,不寫進目標專案的任何檔案。
coding-standards.md 管規則:六種專案檔對應語言、認不出就停下來問;分層看職責不看目錄,
三層各寫功能/邏輯/資料源註解,服務層要標註呼叫的方法讓 reviewer 追得到呼叫鏈;
屬性的用途註解遞迴到每一層,並附真實資料範例,優先取自 MCP,推理來的要明講未經驗證
——不註明的話,會有人照著沒對過的格式寫解析。
comment-styles.md 只管格式:六種語言各一節,都附可照抄的方法註解與屬性註解範例。
Go 的「以識別字開頭」與 Python 的「docstring 在定義的下一行」各自點名,那是最常被
照抄成別的語言寫法的兩處。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:39:48 +00:00
jiantw83 and Claude Opus 5
586b746b55
feat(issue-update): 以 --tick 勾待辦,並在沒東西可改時不送空的 PATCH
...
--tick 收抽取契約交出的那一整行 raw,--section 指出它在哪一個段落。四種擋下來的情況
各有錯誤碼,因為使用者的下一步不同:找不到(抽取結果過期,重抽)、同段落出現多次
(請改寫議題上重複的說法)、那一項沒有方框(去議題上補)、段落不存在(對照輸出確認)。
一律報錯不盲改——改壞了議題的進度條會說謊,而沒有人會去比對 body 的編輯紀錄。
--tick 的輸入只驗「是不是清單項」,不要求方框。抽取端會把忘了寫 checkbox 的項目也收成
一項待辦,那種 raw 要走到 tickLine 才拿得到「去議題上補成 checkbox」這句話;
在入口就擋掉,使用者只會得到一個看不出該怎麼辦的格式錯誤。
沒有任何欄位要改時整個 PATCH 都不送:空的 PATCH 會把議題的 updated_at 推新,在列表上
浮起來像是有人動過。這個判斷做在試跑分支之前——放在後面的話,試跑會預告一個實跑根本
不會發的請求,而那是最難查的那種落差。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:39:44 +00:00
jiantw83 and Claude Opus 5
9582553c41
feat(議題解析): 勾選 checkbox 的精確替換,並統一清單項的文法
...
tickLine 把指定那一行的方框換成已勾,其餘一字不動。四件事決定它會不會靜靜改壞議題:
- **跳過圍欄。** 這是 issue-body.js 全檔的前提,而勾選是本檔唯一會寫回議題的路徑。
工作包模板的架構圖就是一塊 fenced mermaid,裡面出現減號開頭的行是常態,
把它當成待辦勾下去,改壞的是一張圖。
- **限定段落。** 待辦與整體驗收常有一模一樣的一句話,不限定就會回報「分不出來」,
而使用者其實講得很清楚。理由與 upsertLineInSection 相同:弄錯的代價是靜靜改壞內容。
- **整行比對,認不出就交回 ambiguous。** 巢狀待辦底下常有一樣的驗收,賭第一個會讓
進度條指著錯的那一項,而沒有人會去比對編輯紀錄。
- **[ ]、[x]、[X] 指的是同一行。** [X] 是合法的 GFM,Gitea 也渲染成已勾;只認小寫的話,
中斷後重跑會硬失敗,錯誤訊息還會誣指「議題被改過」。
清單項的文法收斂成一份 LIST_ITEM,parseChecklistItem 與 tickLine 共用。先前兩端各寫一份,
鬆緊不一致:`- [ ]甲` 抽得出來卻勾不動,正本那句「一律用 wp-extract 給的 raw」就成了
做不到的指示。沒有方框的項目交回 no-checkbox,不再謊報「已經勾過」。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:39:43 +00:00
jiantw83 and Claude Opus 5
67528dde5f
fix(README): 安裝指令補上 git+ 前綴,否則 npm 會把它當壓縮檔
...
npm 只有看到 git/git+ssh/git+http/git+https/git+file 才會當成 git repo。
寫成 https://….git 會被歸類成遠端 tarball,下載回來解壓失敗,錯誤是
TAR_BAD_ARCHIVE: Unrecognized archive format——看起來完全不像「網址寫法錯了」,
使用者只會以為套件壞了。
先前那條「README 指令逐字執行得動」的測試沒抓到,因為它只跑 tea-sdlc 開頭的
行,npm 那行從來沒被執行過。補一條規則把 .git 網址必須帶 git+ 前綴釘住:
真的去跑一次 npm 全域安裝要開網路、要三秒,不適合放進這套向來密封的測試裡,
但「前綴在不在」這條規則本身就足以擋掉這個錯。
議題 #25
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 15:30:27 +08:00
jiantw83 and Claude Opus 5
cbf4c3df36
docs(README): 前置需求分開講安裝與跑流程,並把指令是否真的跑得動釘住
...
前置需求表原本一句「缺少時腳本印出指引並中止」套在所有需求上,但 install 只
警告不中止——文件描述的是一個不存在的行為。改成逐項寫「缺了會怎樣」,並把
「安裝只需要 Node」提到表前面:使用者不該為了裝 plugin 先去裝 tea。
另外補上三條把 #30 驗收標準真的驗起來的測試。其中一條把 README bash 區塊裡的
tea-sdlc 指令逐行拿去真的執行(家目錄指向空的暫存,所以一個檔都不會寫),
失敗理由若是「這個指令/參數我不認得」就算 README 寫錯——這比用眼睛核對可靠。
議題 #30
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:11:18 +00:00
jiantw83 and Claude Opus 5
002511ce45
test(sdlc-feat): 覆蓋領取鎖決策表、分支命名規則與三處不覆蓋他人進度
...
領取鎖的四種狀態各一例,而且每一種擋的情況都驗「一個字都沒寫進 Gitea」——擋下來卻已經
改了一半,比直接放行更難收拾。
分支命名是純字串規則,表格驅動:開發分支三種寫法、功能分支兩層,加上中文、超長、大寫、
底線與連續連字號的輸入驗證。
git 的部分在臨時 repo 上跑真的 git,釘住三件事後來由 code review 抓出來的實際缺陷:
遠端分支的比對必須用全名(否則 feat/x/main 會冒名頂替 main)、工作區不乾淨要在動手前
就擋、來源分支與遠端分歧要回可區分的錯誤碼而不是 git 的原始訊息。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:07:02 +00:00
jiantw83 and Claude Opus 5
e1897f2d33
test(helpers): 加上「有遠端」的臨時 repo,並收掉三份重複的 git 執行器
...
branch-prep 的重點行為都繞著遠端打轉——來源分支在遠端已存在時要 pull 而不是重建、
目標分支已存在時不能覆蓋他人進度。這些事沒有遠端就驗不出來,所以補一個 bare origin
加工作用 clone,並附 pushFromElsewhere 模擬「別人推了東西上去」。
順手把 makeTempRepo 與新 helper 各自重寫一次的 git 執行器與 seed 收成共用的兩支。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:07:01 +00:00
jiantw83 and Claude Opus 5
d55f1682b7
feat(流程正本): 新增 sdlc-feat 與它的第一段「領取與開工準備」
...
第一段把工作包安全地認領下來、起錶、備妥分支,不改任何一行程式碼——它只負責讓後面的
實作有個乾淨的起點。
正本負責三件腳本做不到的事:讀完工作包後,未處理留言不是 0 就先停下來建議整併;
新分支從哪裡長出來要問過使用者,一次一題、附理由、永遠留手動輸入;議題標題翻成英文
kebab 也是 agent 的事,腳本只驗格式。
領取鎖的四種狀態連同放行那一種都列在表上,缺標籤則另外交代——它是 repo 的前置條件,
不是鎖的第五種狀態,混在一起會讓決策表說不清楚。
工作包跨多個 repo 時逐一確認要在哪幾個開分支,分支名在每個 repo 都相同。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:07:01 +00:00
jiantw83 and Claude Opus 5
d153a49c47
feat(branch-prep): 依命名規則備妥開工的分支
...
命名規則照議題 #1:從開發分支長出 {類型}/{需求描述}/main,從功能分支長出時前兩段沿用
來源,子分支才會留在同一棵樹下。需求描述由議題標題翻譯,那是 agent 的事,所以這裡只收
--slug 並驗格式:英文 kebab、≤40 字元,中文分支名會讓 CI 與 URL 出問題。
三處「不弄丟別人的東西」,而且全部在任何 git 寫入之前判斷完:
- 工作區不乾淨就不動手。未提交的改動與未追蹤的檔案都會跟著 checkout 走到新分支上,
混進這顆工作包的 commit;目標分支已存在時,git 還會拖到 checkout 那一步才拒絕,
屆時 fetch 與 merge 都已經跑掉了。
- 來源分支在遠端已存在時 pull 而不是重建。
- 目標分支已存在時接上去而不是從來源蓋掉。
遠端分支的比對用全名 refs/heads/<ref>:ls-remote 的樣式比對吃的是 ref 尾段,而本 repo
的命名慣例讓每一支分支都以 /main 結尾——用短名比對,拿 main 當開發分支的專案會把
feat/x/main 誤認成 main,整個判成「遠端已經有這一支」。
算與做分成兩段,--dry-run 印出的就是真正將執行的那幾行 git,不另外維護一份描述。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:07:00 +00:00
jiantw83 and Claude Opus 5
7f24c9070e
feat(claim): 領取工作包,上鎖、貼標籤、起錶
...
鎖用 assignee 加標籤,不用碼錶——Gitea 只讓人讀自己的錶,看不到別人的,拿它當鎖會漏判。
碼錶在這裡只有一個用途:發現自己忘了停掉上一顆。
四種狀態的處置:他人已認領擋;自己的錶跑在本議題擋;跑在別的議題擋;沒有鎖放行。
後者包含「自己已認領但沒起錶」,那正是中斷後重跑的情形,重跑不會產生第二把鎖。
兩種碼錶的錯誤碼分開,因為使用者的下一步不同——一個是「你已經在做了」,
另一個是「你忘了停掉那一顆」。
會擋的判斷全部做在任何寫入之前,包含「repo 上有沒有『進行中』標籤」:本 plugin 不自動
建標籤,缺了就整件事不做,不要只設一半的鎖。錶則留到最後才起,前面任一步失敗時不該
留下一顆還在跑的碼錶。
--dry-run 走同一條路,只停在寫入之前。它印出的是這一顆此刻真正缺的那幾步,
不是一份手寫的固定清單——後者會跟實作走鐘,也說不出「這顆已經是你的了」。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:07:00 +00:00
jiantw83 and Claude Opus 5
f524f17375
fix(lib): git 的進度訊息不再漏到 stderr,並讓前置檢查交回帳號
...
兩件都是 branch-prep 與 claim 落地時才浮現的既有缺口:
git 把 checkout/fetch 的進度訊息全寫在 stderr,而 execFileSync 預設讓 stderr 直接
繼承給父行程。腳本的輸出契約是「stdout 一行 JSON、stderr 乾淨」,不收的話呼叫端還得
自己分辨哪幾行是雜訊。改成收進來;失敗時這些內容仍讀得到,錯誤訊息不會因此變模糊。
preflight 的第二層本來就打過 /user 問「我是誰」,卻只把答案丟掉,害呼叫端要再問一次。
改為連同 repo 資訊一起交回去。原本沒有任何呼叫端在用它的回傳值,形狀改變是安全的。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 07:06:59 +00:00
jiantw83 and Claude Opus 5
17178f0c4d
feat(佈署): 以 npm 裝出 tea-sdlc 指令並產生各平台轉接檔
...
單一入口 bin/tea-sdlc.js 認四個子指令。第一個位置參數是子指令,其餘 argv 原樣
交出去——既有的 flag 解析拒絕位置參數,所以子指令必須在那之前就被取走。
轉接檔裡沒有路徑,只有一句 tea-sdlc prompt --name <指令名>。正本在哪由 PATH 上
的 tea-sdlc 自己回推:fnm 把 Node 版號寫進全域安裝路徑,寫死路徑的話升一次
Node,七個平台的轉接檔會同時指向不存在的檔案,而且不會有任何錯誤訊息。
prompt 是全專案唯一輸出非 JSON 的路徑,理由只有一個:它的輸出要餵給模型讀。
失敗仍走 envelope——成功是內容,失敗才需要結構。
status 的 ok 不兼差表達環境好壞,健康與否放在 data.healthy:呼叫端要分得出
「status 掛了」與「status 成功查到你環境有問題」。
install 只寫進偵測得到的平台;缺 git/tea 只警告不中止,因為那兩個完全不影響
轉接檔產生,硬擋等於逼使用者為了裝 plugin 先去裝 tea。uninstall 只刪帶產生標記
的檔案,使用者自己寫的同名檔案一律留著並在輸出裡交代。裝哪些指令以 prompts/ 裡
實際存在的正本為準,不是寫死的六個名字——裝出指向不存在正本的轉接檔,使用者只會
看到 PROMPT_NOT_FOUND。
流程正本的 description 前綴在抄進轉接檔之前就檢查:有三個平台關不掉自動觸發,
全靠那句話把 description 窄到不會被誤判,不能等使用者發現誤觸才知道漏了。
議題 #26 #27 #28 #29 #17
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:59:37 +00:00
jiantw83 and Claude Opus 5
b56216e0f0
test(工時報表): 釘住週次歸屬的跨月、跨年與五個週五
...
時區在測試裡固定為 Asia/Taipei:週界是以人在的時區切的,不釘住時區就等於沒釘住答案。
--today 讓「本週」在 CLI 接縫上釘得住,否則預設期間會跟著系統時鐘漂走。
寫入請求那則斷言誠實列出唯一的非 GET——四層前置檢查的寫入權探針,
而不是先把它濾掉再宣稱整趟沒有寫入。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:50:35 +00:00
jiantw83 and Claude Opus 5
57e45dd63e
feat(流程正本): 新增 sdlc-report 與工時報表模板
...
正本釘住三件事:期間怎麼切、落差怎麼讀、印到哪裡為止。報表只印在終端,
不張貼到議題、PR 或任何管道——要給誰看是使用者的決定,不是這個流程的。
時分格式由腳本算好,正本明令直接取用:報表上的數字自己算錯,比沒有報表更糟。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:50:34 +00:00
jiantw83 and Claude Opus 5
fc30df15f2
feat(工時報表): 以 report.js 產出週/月/年工時報表
...
期間固定以週五當錨點:一週為週一至週日,跨月那一週依該週週五所屬月份歸屬,
一筆工時因此只會落在一個月裡,不會被前後兩個月各算一次。
工時取自 /user/times——它永遠只回傳自己的工時,不必有 issue manager 權限,
repo 的篩選因此在本地做。估算讀的是議題「關聯」段落裡的那一行,不是 Gitea 的
time_estimate 欄位:該欄位的 API 寫不進去,議題上唯一可信的估算就是那一行;
內嵌的議題沒帶 body 時補查一次議題,否則估算會整欄靜靜變成 null。
總計的落差只拿有估算的議題的實際去比。拿全部實際去比只有部分議題的估算,
會讓沒估算的工時全部變成「超出估算」,落差就永遠是灌水的正數。
labelledNumber 放進 issue-body:body 的解析正本在那裡,格式改一次不該動兩個模組。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:50:34 +00:00
jiantw83 and Claude Opus 5
74930b0571
test(wp-extract): 覆蓋契約欄位、模板變體、活狀態與 CRLF
...
解析是整條鏈的上游,解析錯則下游全錯,所以模板變體餵得雜一些:缺段落、巢狀驗收為空、
checkbox 已勾、中英混排、圍欄裡的假待辦、只寫一格的表格列。
另外釘住三件容易在日後鬆掉的事:
- `raw` 逐行出現在原始 body 裡,包括 CRLF 的 body 連行尾的 \r 都留著,否則下游替換
時對不上原文。
- 相依與留言都逐頁讀完,不是只讀第一頁。
- --dry-run 預告的請求順序與實跑一致,且完全不碰 Gitea。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:30:24 +00:00
jiantw83 and Claude Opus 5
4655a30f47
feat(wp-extract): 建立工作包的抽取契約
...
實作階段的指令給一個議題編號就拿得到它需要的一切,不必吞下整份議題全文。
輸出與議題 #1 的契約一致:待辦與它自己的驗收是巢狀的,每一項都帶未經修改的 `raw`,
下游靠它只改那一行、不重寫整份 body。
body 說不出的三個活狀態另外現查:相依走 dependencies/blocks 兩個端點並逐頁讀完
(半份清單會讓下游把實作順序排錯,那比直接報錯更難發現)、領取人看 assignee、碼錶
走 /user/stopwatches。碼錶那一項受限於 Gitea 只讓人讀自己的錶,真正的語意是「我的錶
正跑在這顆議題上」,這是議題 #1 已接受的取捨;領取鎖看的仍是 assignee。
同樣只讀 body 不讀留言,但回報未處理留言數。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:30:23 +00:00
jiantw83 and Claude Opus 5
98861cecbd
feat(議題解析): 解析巢狀待辦、多欄表格與關聯裡的議題編號
...
工作包議題比需求議題多三種結構,抽取契約(#9)要靠它們:
- checklistInSection 收 body 而不收切好的段落。它要交出的 `raw` 是下游勾選 checkbox
時做精確字串替換的依據,那一行必須逐字等於 body 裡的原樣,連縮排與行尾的 \r 都不能
動;段落切分會修掉前後空白,給不出這種保證。兩種畸形寫法都不丟內容:巢狀超過一層攤
進所在待辦的驗收,還沒有上層待辦就先出現的縮排項目升格成待辦。
- tableRows 保留全部欄位,tableSection 改寫成它的兩欄版。介面契約是四欄,先前那一支
只留兩欄。短的資料列補空字串——正本明講「不產出對外介面就寫一列『無』」,那一列不該
與「沒有這一段」混為一談。
- referencedIndex 從關聯段落讀出 `需求議題:#N`。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:30:23 +00:00
jiantw83 and Claude Opus 5
bbfa842e2a
refactor(抽取): 議題讀取與留言計數收進 lib,兩支抽取腳本共用
...
工作包的抽取契約(#9)要做的事與需求議題那一支有三件完全重疊:讀議題、把「不存在」
與「沒有讀取權」分成兩種錯誤碼、以 +1 reaction 數出未整併的留言則數。這三件事的規則
只該有一份,複製一份到新腳本等於日後改規則要記得改兩個地方。
fetchIssue、countUnmergedComments 與試跑時那句附註一起搬到 lib,issue-extract 改為
呼叫它們,行為不變。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:30:22 +00:00
jiantw83 and Claude Opus 5
3977523eb3
fix(議題解析): CRLF 的 body 不再讓清單靜靜變成空的
...
瀏覽器送出 textarea 一律用 CRLF,議題只要在 Gitea 網頁上被編輯過,body 逐行切開後
每一行行尾就掛著 \r。JS 的 . 不吃 \r,`(.*)$` 因此整行比不中——listSection 會把
目標、非目標、驗收標準這些段落一律回成空陣列,而且不報錯,下游拿到的是「這一段沒寫」
而不是「解析失敗」。
改用 [\s\S] 比對行尾,text 本來就有 trim 會把 \r 修掉。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:30:21 +00:00
jiantw83 and Claude Opus 5
a460d7e661
test(總覽網頁): 把三條驗不出東西的斷言改成驗得出來的
...
- mermaid 那條原本只比對「有出現 mermaid 字樣」,而 CDN 網址裡就有這個字,
整段渲染腳本刪掉也照樣通過。改成驗 mermaid.initialize 真的被呼叫。
- 新增一條守住例外的範圍:模板裡只能有那一段腳本,且不得夾帶網路或儲存操作。
- sdlc-plan 的七條斷言原本拿整份正本比對,任何一處撞到就算過。改成只看第 6 步,
與 sdlc-analyze 那邊的做法一致。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:31 +00:00
jiantw83 and Claude Opus 5
197de62570
fix(issue-update): 網址夾帶私有提醒時擋下,並修正 TDZ 錯誤
...
把上一次寫回的整行一起複製貼上是很常見的手誤,放行的話那句提醒會在議題上出現
兩次。
順帶修掉一個自己造出來的錯:PRIVACY_NOTE 原本宣告在 main() 之後,而新的檢查在
main() 的同步段就要用到它,於是每次執行都以「Cannot access 'PRIVACY_NOTE' before
initialization」收場。常數移到 main() 之前。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:30 +00:00
jiantw83 and Claude Opus 5
57c059b5f9
docs(慣例): 寫明總覽模板是「模板不含邏輯」的唯一例外
...
overview-artifact.html 是一份要在瀏覽器裡開的網頁,需要一段把 mermaid 畫出來的
腳本。與其默默違反自己寫的規則,不如把例外與理由寫進慣例表——下一個人才知道
這是想過的決定,而不是漏網的。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:30 +00:00
jiantw83 and Claude Opus 5
0aa0439061
test(總覽網頁): 釘住模板結構與兩份正本的規則
...
模板的檢查重點是「樣式與內容分離」與「全景段落能整段消失」,兩者壞掉時規劃版會
留下空標題或行內樣式散落各處,肉眼不容易發現。
順帶把 work-package 測試的 phase2 切法收斂到第三段之前——原本切到檔尾,第三段
新增的編號清單會混進段落順序的斷言裡。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:29 +00:00
jiantw83 and Claude Opus 5
47529d33c2
test(issue-update): 覆蓋總覽網址的寫回與就地更新
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:29 +00:00
jiantw83 and Claude Opus 5
6b30552ee2
feat(流程正本): 兩份正本加入產生總覽與寫回網址的步驟
...
規劃版填總覽、目標、流程圖,工作包全景填空字串;分析版沿用同一份模板,額外把
工作包的相依與截止日畫成 graph TD 的全景圖。節點一樣以 12 個為上限,超過就只畫
相依鏈最長路徑上的那幾顆。
兩份都寫回同一顆需求議題的同一行,所以分析版會取代規劃版——同一顆需求議題只掛
一個總覽網址,這是預期行為,正本裡寫明免得被當成 bug。
另外交代填模板時流程圖不帶圍欄:圍欄是議題 markdown 用的,填進 HTML 會多出一段
沒有意義的字。
Closes #10
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:28 +00:00
jiantw83 and Claude Opus 5
0e05dd2066
feat(issue-update): 支援 --overview-url,把總覽網址寫回議題
...
沿用既有的 upsertLineInSection:連結以固定前綴獨佔總覽段落裡的一行,重跑時就地
更新,不會長出第二個連結;議題原本的 markdown 白話總覽一字不動——網頁是補充,
不是取代。
連結旁自動附上「此連結預設為私有,組織外無法開啟」。讀到的人多半會想轉寄給組織外
的人,這句話寫在議題上比寫在文件裡有用。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:28 +00:00
jiantw83 and Claude Opus 5
ed2d08de9f
feat(總覽模板): 新增圖解版總覽的 HTML 模板
...
給非技術的利害關係人在會議上直接投影用:一句話總覽、目標、流程圖,分析階段再多
一張工作包全景。
樣式全部集中在單一 style 區塊,內文只放佔位——改版面不必動內容,換內容不必碰樣式。
深色模式跟隨系統,手機寬度另有斷點,投影與傳連結兩種場合都看得清楚。
工作包全景是整段佔位、獨佔一行,規劃階段填空字串時整段會乾淨消失;若把它包在寫死
的 section 裡,規劃版就會留下一個空標題。
mermaid 由 CDN 載入並實際渲染,而不是把原始碼丟給讀者看;代價是離線開啟時圖不會出來。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:22:27 +00:00
jiantw83 and Claude Opus 5
97d0afac38
test(project-add): 把「不建立專案」改成驗得出東西的斷言
...
原本的條件是「沒有非 GET 請求打到路徑含 project 的端點」,但 Gitea 的專案根本
沒有 API 路徑,這個條件恆真,壞掉的實作也會通過。改成斷言真正的寫入只有議題本身
那一次 PATCH、而且只帶 projects 欄位。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:12 +00:00
jiantw83 and Claude Opus 5
15da38a460
test(issue-update): 封住估算那一行的三種污染
...
三條測試都先確認過會在舊版實作上失敗。第一版寫出來時有兩條其實是陪跑的——
情境裡沒有後續段落,正確與錯誤的結果剛好都落在 body 結尾,分不出來。加上一個
真正的後續段落之後才有鑑別力。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:11 +00:00
jiantw83 and Claude Opus 5
b09746a309
test(排程): 覆蓋重複 index
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:11 +00:00
jiantw83 and Claude Opus 5
7a6cf948f5
fix(流程正本): 補回標題後被吃掉的空行
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:10 +00:00
jiantw83 and Claude Opus 5
be6ceddde0
refactor(參數): parseIndex 抽到 lib,四支腳本共用
...
同一段 /^[1-9]\d*$/ 與 BAD_INDEX 已經抄了四份。lib 本來就放著同性質的 parseRepo,
這一支該待在它旁邊。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:10 +00:00
jiantw83 and Claude Opus 5
e5b247e4cf
fix(排程): 拒絕重複的工作包 index
...
同一個 index 出現兩次時,相依看的是後者、標題與人天卻取到前者,算出來的時程是
兩份定義混出來的東西,而且回傳 ok:true 完全不報錯。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:09 +00:00
jiantw83 and Claude Opus 5
e494bd534e
fix(議題解析): upsertLineInSection 限定在目標段落內
...
三個實際重現過的污染情境:
- 圍欄裡的 `## 關聯` 被當成真標題,估算插進圍欄後面。本檔的共同前提是「圍欄裡的
東西不是內容」,這支卻自己用 indexOf 找標題,繞過了那個判斷。
- `## 關聯度說明` 被 indexOf 當成 `## 關聯` 命中,改到別人的段落。
- 「這一行是否已存在」用整份 body 比對,於是別的段落剛好有 `估算人天:` 時被改掉,
真正的關聯段落反而一直拿不到值。
改成沿用同檔的 eachLine 走行、標題要完全相同、既有那一行只在段落範圍內找。
弄錯的代價是靜靜改壞別人的內容,所以三道判斷都收緊。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:09 +00:00
jiantw83 and Claude Opus 5
257642cb1c
test(sdlc-analyze): 釘住第三段的腳本順序與已知限制那一節
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:08 +00:00
jiantw83 and Claude Opus 5
c635465751
feat(sdlc-analyze): 加入第三段,把工作包排上時程與看板
...
先算後寫: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 >
2026-09-17 06:06:08 +00:00
jiantw83 and Claude Opus 5
5327f5882e
test(project-add): 覆蓋反查、貼網址、既有看板不被覆蓋
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:07 +00:00
jiantw83 and Claude Opus 5
770bf686ce
feat(project-add): 把議題放進看板,看板 id 靠掃議題反查
...
Gitea 1.27 沒有「列出專案」的 endpoint,名稱只能反查:掃最近 50 筆議題,從它們
身上的 projects 欄位湊出 id→名稱對照。全都沒掛看板時湊不出來,這時請使用者直接
貼專案網址,結尾即 id。
projects 欄位是整份取代不是附加,所以要先讀出議題既有的看板再聯集——漏掉就等於
把這顆議題踢出原本的看板。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:06 +00:00
jiantw83 and Claude Opus 5
3f97fe0901
test(issue-update): 覆蓋 Milestone 解析、日期格式與估算的就地更新
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:06 +00:00
jiantw83 and Claude Opus 5
45bfd99d80
feat(issue-update): 掛 Milestone、寫截止日、記人天估算
...
三件事經同一個 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 >
2026-09-17 06:06:05 +00:00
jiantw83 and Claude Opus 5
b45437e527
test(issue-link): 覆蓋兩種相依、冪等與自我相依
...
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com >
2026-09-17 06:06:05 +00:00