Commit Graph
86 Commits
Author SHA1 Message Date
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
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
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 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
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 57e45dd63e feat(流程正本): 新增 sdlc-report 與工時報表模板
正本釘住三件事:期間怎麼切、落差怎麼讀、印到哪裡為止。報表只印在終端,
不張貼到議題、PR 或任何管道——要給誰看是使用者的決定,不是這個流程的。

時分格式由腳本算好,正本明令直接取用:報表上的數字自己算錯,比沒有報表更糟。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:50:34 +00:00
jiantw83andClaude 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
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 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
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
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 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
jiantw83andClaude 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
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 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
jiantw83andClaude 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
jiantw83andClaude 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
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 7a6cf948f5 fix(流程正本): 補回標題後被吃掉的空行
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:06:10 +00:00
jiantw83andClaude 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
jiantw83andClaude 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
jiantw83andClaude 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
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 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
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 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
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 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
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
jiantw83andClaude Opus 5 b1025168c0 feat(issue-link): 建立議題之間的阻擋與先決
--depends 是「這顆被誰擋住」,--blocks 是「這顆擋住誰」。先讀現況只補缺的那幾條,
重跑不會在 Gitea 上堆出重複的相依。

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:06:04 +00:00
jiantw83andClaude Opus 5 be477a4391 test(排程): 覆蓋拓撲順序、多重先決與成環
除了逐例比對日期,另有一條測試直接驗「所有先決關係都滿足」這個不變式,
而不是只比對硬編的日期字串——不變式壞掉時它會指出是哪一對。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:06:04 +00:00
jiantw83andClaude Opus 5 0f53567737 feat(排程): 依相依關係拓撲推算截止日
保證一件事:任一工作包的截止日都不早於它的先決。人工排時程最常出現的矛盾就是
前置工作比後續還晚到期,看板上看起來合理、實際上做不到。

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 06:06:03 +00:00
jiantw83andClaude Opus 5 6457d68874 refactor(測試): 模板結構的三項檢查抽到共用斷言
段落順序、正本的編號清單、圖表段落不寫死圍欄——這三件事需求議題與工作包議題
都要驗,第二份模板出現時就該抽出來。兩份測試原本各抄一份,其中佔位數的斷言
還悄悄長成不同寫法(一邊 >=、一邊 ==);抽出來之後這種分歧會被逼著講清楚。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:26 +00:00
jiantw83andClaude Opus 5 9a67c8074f fix(流程正本): 「不畫圖」綁回上限,不是免死金牌
兩份正本原本都只寫「不畫:一行說明為什麼不畫」,讀起來像是隨時可以跳過。
議題 #1 的原意是「超過上限即拆圖或不畫」——退路是給畫不下的情況用的。
改成只在超過上限拆不開、或畫了不會比文字更清楚時才選,兩份正本一起改,
免得兩邊講法不同。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:26 +00:00
jiantw83andClaude Opus 5 7b54d566e2 docs(議題解析): 標明 tableSection 只處理兩欄
工作包的介面契約是四欄(介面/產出者/消費者/形狀),直接沿用這一支會無聲
丟掉第三、四欄。把限制寫在函式註解上,讓接手 wp-extract 的人一眼看到,而不是
自己踩一次才發現。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:25 +00:00
jiantw83andClaude Opus 5 f0beb1ff64 test(工作包): 釘住模板與「產生工作包」那一段的規則
模板的段落順序決定 wp-extract 解析得到什麼,待辦的巢狀寫法決定實作階段勾得到
哪一行——這兩件事寫死在測試裡,改動時才會被逼著一起改。

巢狀範例的斷言不比對固定縮排量,而是比對「最深的一層比最淺的深」,這樣重排
外層清單的縮排不會弄壞測試。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:25 +00:00
jiantw83andClaude Opus 5 0cd25d49d4 feat(sdlc-analyze): 擴充為兩段式,第二段把共識變成工作包議題
第一段到共識摘要為止仍然完全不寫入;使用者點頭之後才進入第二段建立議題。
邊界條文隨之改寫成「共識摘要之前不對 Gitea 產生任何寫入」,並明列第二段
不做的事:不建相依、不掛 Milestone、不加看板、不寫人天估算——那些是後續
流程的工作,寫在這裡會讓兩顆工作包互相踩。

工作包的切法、標題規則(動詞加名詞、禁止 WP-01 這類流水編號)、待辦與驗收
的巢狀寫法(附可照抄的範例)、架構圖依性質三選一,都在這一段定下來。

邊界的斷言跟著條文一起改,因為條文換了語意;分開成兩顆 commit 的話中間那顆
會是紅的。另補一條斷言:第一段不得出現任何寫入型腳本的名字。

Closes #7

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:24 +00:00
jiantw83andClaude Opus 5 35bafcc928 feat(工作包): 新增工作包議題模板
九個段落與順序:這個工作包在做什麼/描述/架構圖/範圍邊界/介面契約/待辦/
整體驗收/repo 列表/關聯。段落順序即下游 wp-extract 的解析依據。

介面契約是四欄表格(介面/產出者/消費者/形狀)並帶分隔列,讓解析有明確的
資料列起點。架構圖同樣不寫死 mermaid 圍欄——正本允許不畫,圍欄寫死時不畫會在
議題頁留下一塊渲染失敗的空區塊。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:43:24 +00:00
jiantw83andClaude Opus 5 8a405de189 test(sdlc-analyze): 覆蓋時程清單的估算前提
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:07 +00:00
jiantw83andClaude Opus 5 b79c68613a fix(可行性檢查): 補上時程估算的前提,並讓四份清單結構一致
時程清單的「相依鏈最長路徑」預設了一份工作拆法,但分析階段還沒有工作包可依,
照著問會問不出東西。補上說明:這個階段要先拉一份暫定拆法,它同時是下一段開
工作包的草稿;拆不出來本身就是一個要問使用者的問題。

架構清單原本在開頭多了一段中間文字,內容與自己的「問題怎麼問」重複,也讓它
成為四份裡唯一結構不同的一份,移除。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:07 +00:00
jiantw83andClaude Opus 5 2b06dfd663 test(sdlc-analyze): 釘住四份清單與正本的結構
這一顆沒有腳本,交付的就是文件本身,所以驗的是文件的結構:四份清單各有足夠
的檢查項與提問指引、正本逐一指名它們且順序為架構→邏輯→資料→時程、一次一題、
兩個固定選項、共識摘要只印不寫,以及「不對 Gitea 寫入」有被寫成明確邊界。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:06 +00:00
jiantw83andClaude Opus 5 930270f545 refactor(測試): 正本的共用檢查抽到 helpers
description 前綴、沒有 frontmatter、不出現平台專屬字樣——這三件事每一份流程
正本都要驗,第二份正本出現時就該抽出來,而不是再抄一次。順帶把讀正本、讀規則
正本、讀模板三個路徑組合也收在同一處。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:06 +00:00
jiantw83andClaude Opus 5 775909bd3d feat(sdlc-analyze): 新增可行性分析的流程正本
一次問一題、依架構→邏輯→資料→時程清空、每題固定給「建議(含理由)」與
「手動輸入」兩個選項、最後輸出共識摘要。摘要只印在終端,這一段完全不寫入
Gitea——把工作包開出去是下一段的事。

開頭先看 issue-extract 回傳的未處理留言數:不是 0 就代表描述可能是過期的,
先提示使用者以 sdlc-sync 整併回描述再回來,堅持要繼續就在摘要裡註明。

明令「能在程式碼裡查證的就自己去查」,把問題留給只有人能回答的事。

Closes #6

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:05 +00:00
jiantw83andClaude Opus 5 549bd29313 feat(可行性檢查): 新增架構、邏輯、資料、時程四份檢查清單
四份規則正本,各自列出要對著需求議題回答的檢查項,回答不出來的就是一個要問
使用者的問題。每份末尾都交代「問題怎麼問」——檢查項只說要查什麼,不說怎麼把
它變成一句能收斂的提問,那才是實際卡住的地方。

四份各自點出一個最該先問的:架構問落點與循環相依,邏輯問既有功能是不是已經
做過同一件事(這條能整個取消工作),資料問 schema 與遷移(答案通常不在議題裡),
時程問未知數最大的一項(它決定整體估算的可信度)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:11:05 +00:00
jiantw83andClaude Opus 5 06de8980ca test(issue-extract): 封住圍欄、表格與分頁的回頭路
八條新案例,每一條都對應一個實際重現過的缺陷:~~~ 圍欄、圍欄內的假清單項、
混用圍欄標記、未閉合圍欄的歸屬、逸脫的直線、第二條分隔列、巢狀攤平,以及
留言分頁。

把修正還原成舊版解析器後,這批測試有五條會失敗;修正回來則全綠——確認測試
真的抓得到,不是陪跑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:29 +00:00
jiantw83andClaude Opus 5 6125c2b6e0 fix(issue-extract): 留言逐頁讀完,不再只數第一頁
原本只打一次留言端點就收工,留言超過一頁時未整併的則數會少算——而少算的後果
是下游以為描述是最新的,照著過期的描述做事。改用 lib.pages 走完所有頁,讀不完
就以 COMMENT_LIMIT 報錯,不無聲回傳半份。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:28 +00:00
jiantw83andClaude Opus 5 c8ecc39dc8 refactor(分頁): 把逐頁走訪抽成 lib.pages
findIssueByTitle 原本自己寫了一份「翻到短頁為止、超過上限就報錯」的迴圈,
而新的留言走訪需要同一套規則。抽成非同步產生器之後,呼叫端仍能在找到目標時
提早離開,規則卻只寫一次。

錯誤碼與訊息由呼叫端指定:查重讀不完要講的是「可能重建議題」,數留言讀不完
要講的是「數不完未整併的則數」,處置不同就不該共用一句話。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:28 +00:00
jiantw83andClaude Opus 5 a5f28ec2c8 fix(議題解析): 圍欄與表格的邊界,五個會污染下游的解析缺陷
code review 逐項驗出來的,全部可重現:

- `~~~` 圍欄完全沒被認出來,裡面的井字號會被當成段落標題。
- 段落內的圍欄不影響清單解析,於是程式碼範例裡的減號變成假的驗收標準。
  這是最嚴重的一個——輸出多出一條沒有人寫過的標準。
- 表格欄位裡逸脫的直線 `\|` 會把欄位切斷,內容整段消失。
- 同一段落裡若出現第二條分隔列,它會變成一筆 {term:'---'} 的假名詞。
- 圍欄開了沒關時,其後內容的歸屬沒有明確定義。

改法是把「圍欄裡的東西不是內容」這件事收斂到 eachLine 處理一次,段落切分、
清單、表格三者都靠它,而不是各自寫一份半套的判斷。圍欄需同種標記才算關閉;
沒關就到結尾時,其後內容一律算在圍欄內,這與 markdown 的實際渲染一致。

巢狀清單改為明確攤平並寫進註解:需求議題的模板沒有巢狀,真的出現時寧可多帶
一項,也不要無聲吃掉內容。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:28 +00:00
jiantw83andClaude Opus 5 fd80d9772a test(issue-extract): 以模板變體覆蓋抽取契約
解析是整條鏈的上游,解析錯則下游全錯,所以模板變體餵得比別處雜:缺段落、
段落在但列表是空的、checkbox 已勾與未勾、中英混排、編號清單、名詞表只有
表頭、body 全空、出現契約外的段落。

留言的部分驗兩件事:未整併則數只算沒有 +1 的,以及留言內容一個字都不得出現
在輸出裡——後者用整份 stdout 做子字串比對,比逐欄檢查更難繞過。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:27 +00:00
jiantw83andClaude Opus 5 6254a6b53d feat(issue-extract): 建立需求議題的抽取契約
下游(分析、實作)唯一的議題讀取管道,存在的理由是「不必吞下整份議題全文」。
輸出契約上的十四個欄位,缺少的段落回傳空值。

只讀 body,不把留言內容納入輸出,但回報未整併的留言則數,好讓下游知道自己是
不是在拿過期的描述做事。已整併的留言由 sdlc-sync 打上 +1 reaction,而 Gitea
的留言物件不含 reaction,只能逐則再查一次——請求數會隨留言數增長,但這個數字
要準。

議題不存在與沒有讀取權分成兩個錯誤碼:前者是輸入錯,後者要去改權限,處置不同
就不該共用一個碼。

Closes #5

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:27 +00:00
jiantw83andClaude Opus 5 0bac5ee186 feat(議題解析): 把議題 body 的 markdown 解析抽成純函式
段落切分、列表、兩欄表格三種解析,需求議題與工作包議題共用同一套,
所以獨立成一支不碰網路也不碰檔案系統的模組,而不是塞進 lib。

段落切分會追蹤圍欄狀態:mermaid 或程式碼區塊裡的井字號不得被當成標題,
否則流程圖一畫,後面的段落就全被切碎。

缺段落回傳空值而非報錯——缺段落是模板的正常變體,不是解析失敗。
名詞表以分隔列為界,之後才是資料列,避免把表頭當成一筆名詞。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 05:00:26 +00:00
jiantw83andClaude Opus 5 9b9ab33a53 test(issue-create): 覆蓋建立議題與試跑預覽的契約
沿用既有接縫:子行程執行、stub server 錄下每一筆請求。涵蓋標籤名稱解析、
未知標籤中止、不碰 labels 的寫入端點、同標題不重建、前後空白視為同一顆,
以及寫入型腳本一樣跑滿前置檢查。

試跑的部分特別驗「預覽要忠實」:預覽的 body 必須看得出標籤會被貼上、標籤
錯字在試跑就該擋下、同名議題已存在時預覽不得預告要建立議題,且全程不發出
任何寫入請求。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:47 +00:00
jiantw83andClaude Opus 5 9e57a5db99 test(sdlc-plan): 釘住正本與模板的結構
正本與模板是檔案而非程式,卻是本工作包實際交付的東西:模板段落順序決定下游
解析得到什麼,正本的平台中立性決定轉接檔能不能一份寫到底。以測試釘住段落
順序、佔位格式、description 前綴、平台專屬字樣的缺席、流程圖的上限,以及
模板不得寫死 mermaid 圍欄。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:47 +00:00
jiantw83andClaude Opus 5 3f4e1a683b feat(issue-create): 建立議題,標籤限既有、以標題查重
標籤以名稱指定,送出前換成 repo 上既有標籤的 id;指到不存在的標籤即中止並列出
可選項目。本工具不具備建立標籤的能力,錯字當場講清楚,不悄悄少貼一個。

以標題查重,同名議題已存在就回傳既有那一顆並把 created 設為 false,讓中斷後
重跑不產生重複議題。先驗標籤再查重:參數打錯要立刻講。

--dry-run 不寫入,但會讀。要讓預覽忠實反映將送出的請求,就得先把標籤名稱換成
id、也得先查過重——否則預覽看起來會建一顆議題,實跑卻是 no-op,或反過來實跑
才爆標籤錯字。同名議題已存在時,預覽的 requests 為空陣列並附上既有議題編號。

body 由 --body-file 讀入後原樣送出,避免長 markdown 擠在命令列上。

Closes #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:46 +00:00
jiantw83andClaude Opus 5 453cf47188 feat(sdlc-plan): 新增需求議題模板與流程正本
templates/requirement-issue.md 固定需求議題的九個段落與順序:總覽/背景/
目標/非目標/領域名詞表/流程圖/驗收標準/影響範圍/未決事項。段落順序即
下游 issue-extract 的解析依據,總覽段落預留總覽網頁的連結佔位。

流程圖段落只放佔位、不寫死 mermaid 圍欄:正本允許「乾脆不畫」,若圍欄寫死在
模板裡,不畫時就會在議題頁留下一塊渲染失敗的空 mermaid 區塊。圍欄改由填入的
內容自己帶。

prompts/sdlc-plan.md 是流程正本,平台中立 markdown,不含任何平台專屬語法,
description 以「僅由 /sdlc-plan 指令叫用。」起頭。正本交代:三種輸入來源、
一次問一題且不得替使用者編造、九個段落的填法、Mermaid flowchart 的 12 節點與
8 字上限、標籤只能從 labels-list 挑、寫入前先 --dry-run。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:46 +00:00
jiantw83andClaude Opus 5 b0bd061852 refactor(測試): 共用假 Gitea 的啟動與環境變數
每支測試各自寫一次「啟動 stub、登記 close、組環境變數」已經重複三次,抽到
helpers 之後新增測試檔只要一行。各檔仍保留自己的預設路由,因為那是該檔的
情境設定,不該共用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:45 +00:00
jiantw83andClaude Opus 5 4f542d9c86 refactor(標籤): 把列標籤抽到 lib,讓要貼標籤的腳本共用
labels-list 與後續要挑標籤的腳本都要打同一個端點,抽成 lib.listLabels 之後
端點只寫在一個地方。順帶把「本專案不建立標籤,這是取得標籤的唯一途徑」寫進
函式註解,讓下一個要加功能的人先看到這條界線。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:44:45 +00:00
jiantw83andClaude Opus 5 4ae097f1e1 fix(腳本契約): 長輸出不再被截斷,查重翻頁加上上限
process.stdout.write 之後立刻 process.exit 會截斷輸出——stdout 接到 pipe 時
寫入是非同步的。改為等 write 的 callback 回來再退出。實測舊寫法在約 83KB 處
被切斷,新增的回歸測試以 8000 筆標籤覆蓋這條路徑。

findIssueByTitle 的翻頁原本沒有上限,Gitea 若持續回滿一頁就會無限打下去。
加上 200 頁上限,超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會
讓呼叫端把既有議題再建一次,正好是冪等查重要防的事。

parseFlags 取值時不再於三元運算式內遞增迴圈變數,改為獨立敘述。

測試工具的 maxBuffer 調高到 64MB:預設 1MB 會在長輸出時砍掉子行程,那是測試
工具的限制而非腳本的問題。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:26:51 +00:00
jiantw83andClaude Opus 5 28dec83498 test(腳本契約): 以子行程與 stub server 驗證契約與四層前置檢查
測試一律以子行程執行腳本、比對 stdout 的單行 JSON 與 exit code,因為那正是
七個平台共用的實際呼叫方式,不會因內部重構而破碎。

Gitea 以本機 stub server 替身並錄下每一筆請求,藉此斷言「腳本到底發了哪些
請求」——包含 --dry-run 不得發出任何請求、前一層檢查沒過就不再往下打、
寫入權探針不得挾帶任何要寫入的欄位。git 則在 .tmp/ 下的臨時 repo 跑真實
指令,比 mock 可信且成本低。

lib-exports.test.js 是唯一直接 import lib 的例外:冪等查重與 git 執行點在
CLI 邊界上還沒有消費者,檔頭已註明等 #4 與 #10 落地後即可縮小或移除。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:26:51 +00:00
jiantw83andClaude Opus 5 48fd69cfde feat(腳本契約): 建立共用函式庫、四層前置檢查與 labels-list
scripts/lib.js 立起所有腳本共用的地基:具名 flag 解析、單行 JSON 輸出
{ok, data, error:{code, message}}、Gitea API 與 git 各自唯一的出口、
四層前置檢查、依標題的冪等查重。

前置檢查依序為執行環境(git/tea 在 PATH 上、plugin 目錄完整)、Gitea 登入
有效、帳號對 issues unit 的寫入權、repo 已開啟時間追蹤;任一層不通過即帶著
可區分的錯誤碼中止,訊息一律指出該去哪裡改設定,前一層沒過就不再往下打。

issues unit 的寫入權採實測而非讀 permissions.push——Gitea 的 team unit 權限
可獨立於 repo 的 push 權限。探針打在不存在的議題 index 0:有寫入權會通過權限
中介層後回 404,沒有則直接 403,兩種結果都不改動任何東西。

認證預設沿用使用者既有的 tea login(讀 tea 的 config.yml),環境變數
TEA_SDLC_API_BASE / TEA_SDLC_TOKEN 只作為測試與 CI 的覆寫出口。

labels-list 為第一支腳本,刻意只讀不寫,呼應「不自動建立 Gitea 標籤」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:26:50 +00:00
jiantw83andClaude Opus 5 7a8222a053 chore(骨架): 把模板 scaffold 換成 tea-sdlc 骨架
五份 manifest 的 name、description、homepage、repository 由 jsc-template
改指向 tea-sdlc,版本另起為 0.0.1;移除範例用的 skills/hello 並清掉
AGENTS.md 與 README.md 對它的引用。

建立後續工作包要落腳的四個目錄(prompts、scripts、templates、references)
並以 .gitkeep 納入版控;AGENTS.md 改寫為 tea-sdlc 的模組邊界與慣例,腳本
介面契約不複寫而指回議題 #1,避免正本分家。

新增 package.json 作為測試入口(node --test,Node 內建執行器,無任何相依
套件);.gitignore 加入測試暫存目錄 .tmp/ 與 node_modules/。

Closes #2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:06:09 +00:00
jiantw83 21f1103a9a Initial commit 2026-09-17 03:25:38 +00:00