feat/issue-extract-contract/main
master
建立需求議題的抽取契約:下游只要一支指令就能拿到它需要的欄位,不必吞下整份議題全文。
#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組
#5 — 建立需求議題的抽取契約
scripts/issue-body.js
wp-extract
scripts/issue-extract.js
scripts/lib.js
pages
eachLine
下游(分析、實作)原本得重讀整份議題才能取出兩三個欄位,既浪費額度也不可靠。抽取契約把這件事變成一次呼叫、一份精簡 JSON。
新增。findIssueByTitle 改用共用的 pages,行為不變(錯誤碼仍為 DEDUPE_LIMIT),既有測試全數通過。
findIssueByTitle
DEDUPE_LIMIT
npm test:
npm test
ℹ tests 110 ℹ suites 0 ℹ pass 110 ℹ fail 0 ℹ duration_ms 2837
七顆 commit 逐一 checkout 後跑測試,每一顆都是綠的(102/102/102/102/102/102/110)。
Code review 抓到五個解析缺陷,全部先重現再修,並確認新測試抓得到:
1) ~~~ 圍欄: [ '流程圖', '假標題' ] ← 圍欄內的井字號被當成標題 2) 圍欄內的清單: [ '程式碼裡的假項目', '真正項目' ] ← 輸出多出沒人寫過的驗收標準 3) 逸脫的直線: [{"term":"a\\","def":"b"}] ← 欄位被切斷、內容消失 4) 第二條分隔列: [...,{"term":"---","def":"---"},...] 5) 未閉合圍欄: 歸屬未定義
把 issue-body.js 還原成修正前的版本,新測試有五條失敗;改回修正版則 29 條全綠——確認測試真的抓得到,不是陪跑。另外 countUnmergedComments 原本只讀第一頁留言,超過一頁就少算,也已修正並加上分頁測試。
issue-body.js
countUnmergedComments
真實 Gitea 驗證(皆為讀取):
$ node scripts/issue-extract.js --repo plugins/tea-sdlc --index 5 --dry-run {"ok":true,"data":{"dryRun":true,...,"note":"每則留言還會各查一次 reaction..."}} # 直接把議題 #5 的真實 body 餵給解析器 段落: 母議題、要做出什麼、驗收標準、阻擋於 驗收標準: - 輸出欄位與契約完全一致,缺少的段落回傳空值而非報錯 - `未處理留言數` 反映尚未被標記為已整併的留言則數 - 只讀議題 body,不把留言內容納入輸出 - 模板變體(缺段落、巢狀項目為空、checkbox 已勾、中英混排)各有測試案例並通過 - 議題不存在或無讀取權時回傳可區分的錯誤碼
完整實跑路徑仍未在真實 repo 上執行——該 repo 的時間追蹤還沒開,第四層前置檢查會擋下。
驗收標準寫的「巢狀項目為空」在需求議題上不成立:templates/requirement-issue.md 的五個清單段落都是平的,巢狀只存在於 #8 的工作包契約裡。我改成測「巢狀出現時一律攤平、不無聲吃掉內容」,把這條的意圖補在能成立的地方。若你要的是別的語意,說一聲。
templates/requirement-issue.md
🤖 Generated with Claude Code
段落切分、列表、兩欄表格三種解析,需求議題與工作包議題共用同一套, 所以獨立成一支不碰網路也不碰檔案系統的模組,而不是塞進 lib。 段落切分會追蹤圍欄狀態:mermaid 或程式碼區塊裡的井字號不得被當成標題, 否則流程圖一畫,後面的段落就全被切碎。 缺段落回傳空值而非報錯——缺段落是模板的正常變體,不是解析失敗。 名詞表以分隔列為界,之後才是資料列,避免把表頭當成一筆名詞。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
下游(分析、實作)唯一的議題讀取管道,存在的理由是「不必吞下整份議題全文」。 輸出契約上的十四個欄位,缺少的段落回傳空值。 只讀 body,不把留言內容納入輸出,但回報未整併的留言則數,好讓下游知道自己是 不是在拿過期的描述做事。已整併的留言由 sdlc-sync 打上 +1 reaction,而 Gitea 的留言物件不含 reaction,只能逐則再查一次——請求數會隨留言數增長,但這個數字 要準。 議題不存在與沒有讀取權分成兩個錯誤碼:前者是輸入錯,後者要去改權限,處置不同 就不該共用一個碼。 Closes #5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
解析是整條鏈的上游,解析錯則下游全錯,所以模板變體餵得比別處雜:缺段落、 段落在但列表是空的、checkbox 已勾與未勾、中英混排、編號清單、名詞表只有 表頭、body 全空、出現契約外的段落。 留言的部分驗兩件事:未整併則數只算沒有 +1 的,以及留言內容一個字都不得出現 在輸出裡——後者用整份 stdout 做子字串比對,比逐欄檢查更難繞過。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
code review 逐項驗出來的,全部可重現: - `~~~` 圍欄完全沒被認出來,裡面的井字號會被當成段落標題。 - 段落內的圍欄不影響清單解析,於是程式碼範例裡的減號變成假的驗收標準。 這是最嚴重的一個——輸出多出一條沒有人寫過的標準。 - 表格欄位裡逸脫的直線 `\|` 會把欄位切斷,內容整段消失。 - 同一段落裡若出現第二條分隔列,它會變成一筆 {term:'---'} 的假名詞。 - 圍欄開了沒關時,其後內容的歸屬沒有明確定義。 改法是把「圍欄裡的東西不是內容」這件事收斂到 eachLine 處理一次,段落切分、 清單、表格三者都靠它,而不是各自寫一份半套的判斷。圍欄需同種標記才算關閉; 沒關就到結尾時,其後內容一律算在圍欄內,這與 markdown 的實際渲染一致。 巢狀清單改為明確攤平並寫進註解:需求議題的模板沒有巢狀,真的出現時寧可多帶 一項,也不要無聲吃掉內容。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
findIssueByTitle 原本自己寫了一份「翻到短頁為止、超過上限就報錯」的迴圈, 而新的留言走訪需要同一套規則。抽成非同步產生器之後,呼叫端仍能在找到目標時 提早離開,規則卻只寫一次。 錯誤碼與訊息由呼叫端指定:查重讀不完要講的是「可能重建議題」,數留言讀不完 要講的是「數不完未整併的則數」,處置不同就不該共用一句話。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
原本只打一次留言端點就收工,留言超過一頁時未整併的則數會少算——而少算的後果 是下游以為描述是最新的,照著過期的描述做事。改用 lib.pages 走完所有頁,讀不完 就以 COMMENT_LIMIT 報錯,不無聲回傳半份。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
八條新案例,每一條都對應一個實際重現過的缺陷:~~~ 圍欄、圍欄內的假清單項、 混用圍欄標記、未閉合圍欄的歸屬、逸脫的直線、第二條分隔列、巢狀攤平,以及 留言分頁。 把修正還原成舊版解析器後,這批測試有五條會失敗;修正回來則全綠——確認測試 真的抓得到,不是陪跑。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No dependencies set.
The note is not visible to the blocked user.
摘要
建立需求議題的抽取契約:下游只要一支指令就能拿到它需要的欄位,不必吞下整份議題全文。
需求議題
#1 — tea-sdlc:以 tea 驅動 SDLC 全流程的跨平台指令組
工作包議題
#5 — 建立需求議題的抽取契約
變更內容
scripts/issue-body.js— 議題 body 的 markdown 解析(段落、清單、兩欄表格),純函式,#8 的wp-extract會共用。scripts/issue-extract.js— 抽取契約的十四個欄位,外加未整併留言則數。scripts/lib.js— 新增pages非同步產生器,逐頁走訪收斂成一套規則。設計重點
eachLine,段落切分/清單/表格三者都靠它。解決的問題
下游(分析、實作)原本得重讀整份議題才能取出兩三個欄位,既浪費額度也不可靠。抽取契約把這件事變成一次呼叫、一份精簡 JSON。
影響的功能
新增。
findIssueByTitle改用共用的pages,行為不變(錯誤碼仍為DEDUPE_LIMIT),既有測試全數通過。測試結果
npm test:七顆 commit 逐一 checkout 後跑測試,每一顆都是綠的(102/102/102/102/102/102/110)。
Code review 抓到五個解析缺陷,全部先重現再修,並確認新測試抓得到:
把
issue-body.js還原成修正前的版本,新測試有五條失敗;改回修正版則 29 條全綠——確認測試真的抓得到,不是陪跑。另外countUnmergedComments原本只讀第一頁留言,超過一頁就少算,也已修正並加上分頁測試。真實 Gitea 驗證(皆為讀取):
完整實跑路徑仍未在真實 repo 上執行——該 repo 的時間追蹤還沒開,第四層前置檢查會擋下。
待確認
驗收標準寫的「巢狀項目為空」在需求議題上不成立:
templates/requirement-issue.md的五個清單段落都是平的,巢狀只存在於 #8 的工作包契約裡。我改成測「巢狀出現時一律攤平、不無聲吃掉內容」,把這條的意圖補在能成立的地方。若你要的是別的語意,說一聲。🤖 Generated with Claude Code
code review 逐項驗出來的,全部可重現: - `~~~` 圍欄完全沒被認出來,裡面的井字號會被當成段落標題。 - 段落內的圍欄不影響清單解析,於是程式碼範例裡的減號變成假的驗收標準。 這是最嚴重的一個——輸出多出一條沒有人寫過的標準。 - 表格欄位裡逸脫的直線 `\|` 會把欄位切斷,內容整段消失。 - 同一段落裡若出現第二條分隔列,它會變成一筆 {term:'---'} 的假名詞。 - 圍欄開了沒關時,其後內容的歸屬沒有明確定義。 改法是把「圍欄裡的東西不是內容」這件事收斂到 eachLine 處理一次,段落切分、 清單、表格三者都靠它,而不是各自寫一份半套的判斷。圍欄需同種標記才算關閉; 沒關就到結尾時,其後內容一律算在圍欄內,這與 markdown 的實際渲染一致。 巢狀清單改為明確攤平並寫進註解:需求議題的模板沒有巢狀,真的出現時寧可多帶 一項,也不要無聲吃掉內容。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>