Commit Graph
131 Commits
Author SHA1 Message Date
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 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
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 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
admin d0e7e569a3 Merge pull request 'feat/pr-watch-and-cleanup/main' (#48) from feat/pr-watch-and-cleanup/main into master
Reviewed-on: #48
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 09:09:53 +00: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
jiantw83andClaude 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
admin 86bdfd9007 Merge pull request 'feat/pr-comments-and-fix/main' (#47) from feat/pr-comments-and-fix/main into master
Reviewed-on: #47
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 08:48:23 +00: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
admin 3084c91041 Merge pull request '以 worktree 建立工作包分支並備妥隔離環境' (#46) from feat/worktree-branch-prep/main into master
Reviewed-on: #46
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 08:31:54 +00:00
jiantw83andClaude 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
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
admin 03c368b6de Merge pull request 'feat/commit-split-and-pr/main' (#45) from feat/commit-split-and-pr/main into master
Reviewed-on: #45
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 08:25:36 +00: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
admin 1c6fb8aaad Merge pull request 'feat/implement-and-tick-todos/main' (#44) from feat/implement-and-tick-todos/main into master
Reviewed-on: #44
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 07:42:51 +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 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
admin 1e93b77a6d Merge pull request 'fix/npm-git-url/main' (#43) from fix/npm-git-url/main into master
Reviewed-on: #43
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 07:32:00 +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
admin 9644b6a638 Merge pull request 'docs/npm-deploy-alignment/main' (#39) from docs/npm-deploy-alignment/main into master
Reviewed-on: #39
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 07:14:57 +00: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
admin 6cf6a1e36e Merge pull request 'feat/claim-and-branch-prep/main' (#37) from feat/claim-and-branch-prep/main into master
Reviewed-on: #37
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-17 07:10:14 +00:00