Commit Graph
64 Commits
Author SHA1 Message Date
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
admin 643293b21c Merge pull request 'feat/comments-merge/main' (#52) from feat/comments-merge/main into master
Reviewed-on: #52
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 09:22:58 +00:00
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude Opus 5 5ef25d3ee9 test(抽取契約): 未整併的判定改成只認自己打的 +1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 09:19:08 +00:00
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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 a1bbd78cb4 test(sdlc-fix-assets): 新增 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 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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 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 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 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 513dad7f6a test(sdlc-feat-assets): 加入第三段「提交與開立 PR」
PR 描述的八個段落與順序寫在正本裡,由 pr-create 擋;正本負責的是腳本擋不住的事:
測試結果要貼實際輸出而不是改寫成一句話、被擋下來時補真的內容而不是為了通過而拼湊、
以及跨兩個功能時用 --files 分兩次跑。

先開 PR 再停錶的理由也寫進去了:工時要記在真的有做事的那段時間上。
2026-09-17 08:23:23 +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 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude Opus 5 b09746a309 test(排程): 覆蓋重複 index
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:06:11 +00:00
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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