feat/worktree-branch-prep/main
master
領取工作包時不再在原地切換分支,而是一律建立一棵屬於這顆工作包的獨立工作樹。 分支與工作樹合併為同一個原子動作,起點一律取自遠端,碼錶則後移到工作樹建成之後才起。
#38 — 以 worktree 隔離平行工作包的實作與修正
#40 — 以 worktree 建立工作包分支並備妥隔離環境
scripts/branch-prep.js
git fetch origin
git worktree add --no-track -b {新分支} {推導路徑} origin/{來源分支}
--repo
scripts/lib.js
worktreePath
listStopwatches
stopwatchOnIssue
stopwatchElsewhere
scripts/timer.js
scripts/claim.js
scripts/wp-extract.js
prompts/sdlc-feat.md
commit-split --path
AGENTS.md
.git/worktrees/
test/worktree-path.test.js
test/timer.test.js
test/branch-prep.test.js
claim
sdlc-feat-assets
為什麼工作樹是一律的,不是「有衝突才用」。 原地切分支有三種損耗:未提交的變更 擋路、建置產物跨分支混淆,以及 agent 讀到不屬於它那顆工作包的程式碼。前兩種人會 當場發現,第三種不會——agent 是非同步的,它可能在分支已經被切走之後才去讀檔, 而且不會察覺,產出看起來完全合理,只是接錯了上下文。
為什麼一定要 --no-track。 這不是理論問題:開這棵工作樹時 git 就自動把 upstream 設成了 origin/master。此刻遠端還沒有這支新分支,upstream 只會落在來源分支上, 之後 git pull 會把來源分支的提交拉進來。upstream 留給第一次 push -u 自然建立。
--no-track
origin/master
git pull
push -u
為什麼要回滾。 實測 git worktree add 失敗時仍會把新分支留下來。那是最難查的 半成品:下一次重跑會走到「目標分支已存在」那條路,起點從此不再是遠端的來源分支。 測試以「家目錄被一個檔案擋住」造出這個情境,並反向驗證過——拿掉回滾就紅。
git worktree add
為什麼路徑取雜湊而不是把斜線攤平。 攤平會讓 feat/a-b/main 與 feat/a/b/main 撞成同一個目錄,而現行的分支命名規則恰好讓這種形狀有機會出現。
feat/a-b/main
feat/a/b/main
為什麼起錶要後移。 工作樹建立失敗會中止整個領取。錶要是照舊在領取那一步就起, 使用者會被計一段什麼都沒做的時間,而工時要準正是 /sdlc-report 的立足點。
/sdlc-report
工作區不乾淨不再是阻擋條件。 DIRTY_WORKTREE 整條拿掉——讓未提交的變更不再擋路, 本來就是工作樹要解決的事。
DIRTY_WORKTREE
同時持有多顆工作包的人不必再為了換一顆而先處理未提交的變更;每顆工作包有自己的 依賴與建置產物;agent 無論什麼時候讀檔都只讀得到它那顆工作包的程式碼。 另外,被碼錶擋下來時會明說停錶不會動到既有的工作樹(#38 使用者故事第 30 條)—— 以為停錶等於放棄工作包的人會寧可不停,工時就記到別顆去了。
/sdlc-feat 第一段,以及第二、三段的工作目錄——它們的 --path 與實作動作都改指工作樹。branch-prep 的介面有兩處不相容的變更:新增必填 --repo, 以及不再有 DIRTY_WORKTREE。正本已同步,沒有其他呼叫端。
/sdlc-feat
--path
branch-prep
#11 驗收標準第 4 條(來源分支在遠端已存在時 pull 而非重建)依 #38 由本工作包取代, #11 本身不修改;第 5 條的分支命名規則不變。
#11
npm test 633 pass / 0 fail(已 rebase 到含 #45 的 master 之上)。工作樹相關的測試都在臨時 git repo 上跑真的 git, 以本機裸 repo 充當遠端,不需網路。
npm test
另外對真實 repo 試跑過一次:
node scripts/branch-prep.js --repo plugins/tea-sdlc --path /root/plugins/tea-sdlc \ --source master --type feat --slug smoke-test --dry-run → git fetch origin git worktree add --no-track -b feat/smoke-test/main ~/.tea-sdlc/worktrees/72765a939540 origin/master
🤖 Generated with Claude Code
同一份 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>
原本的順序是「放行 → 設 assignee 與標籤 → 起錶 → 處理分支」,而工作樹建立 失敗會中止整個領取——錶已經起了才失敗,使用者會被計一段什麼都沒做的時間, 而工時要準正是工時報表的立足點。 claim 只留領取鎖的兩件事(assignee 與標籤),起錶交給新的 timer.js,由流程 正本排在 branch-prep 之後。timer 已經跑在這顆議題上時什麼都不做:中斷後重跑 是它最常見的處境,重新起錶會把已經累積的時間切成兩段;跑在別顆上則照舊擋下, 不代勞停錶。 三支腳本讀碼錶的那段各留一份,趁這次收進 lib。 議題 #40 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
正本跟著實作走:分支那一步改成工作樹,新增起錶那一步排在它後面, 並把三處「不要自己決定」寫明——來源分支不存在時不自己換一支、路徑被佔住時 不自己刪、工作樹建不起來時不退回原地切分支。工作區不乾淨的處置整段拿掉: 工作樹本來就是為了讓未提交的變更不再擋路。 AGENTS.md 補上兩條邊界。git worktree add 一定會在目標 repo 的 .git/worktrees/ 底下寫中繼資料,這是 git 的機制,無法避免——「不改目標專案」指的是專案的內容檔, 把這件事明說,免得下一個人以為實作違規。 議題 #40 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
議題 #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>
轉接檔的一行說明是從這裡取的,順序寫錯會讓人以為錶還是在領取那一步起。 議題 #40 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dbce8dbd3c
d3ce364727
No dependencies set.
The note is not visible to the blocked user.
摘要
領取工作包時不再在原地切換分支,而是一律建立一棵屬於這顆工作包的獨立工作樹。
分支與工作樹合併為同一個原子動作,起點一律取自遠端,碼錶則後移到工作樹建成之後才起。
需求議題
#38 — 以 worktree 隔離平行工作包的實作與修正
工作包議題
#40 — 以 worktree 建立工作包分支並備妥隔離環境
變更內容
scripts/branch-prep.js:改寫。git fetch origin後以單一git worktree add --no-track -b {新分支} {推導路徑} origin/{來源分支}完成;失敗時回滾分支與目錄;建好後回報乾淨工作樹與偵測到的安裝指令。新增必填
--repo。scripts/lib.js:新增worktreePath(路徑推導)、listStopwatches/stopwatchOnIssue/stopwatchElsewhere(三支腳本共用的碼錶讀取與擋路訊息)。scripts/timer.js:新增。起錶,且已在計時時什麼都不做。scripts/claim.js:拿掉起錶,只留領取鎖的 assignee 與標籤。scripts/wp-extract.js:碼錶讀取改用 lib 的共用函式。prompts/sdlc-feat.md:第一段改成「讀 → 領 → 備妥工作樹 → 起錶」,共七步;第一行 description 的順序跟著改。第二段補上「改的是工作樹裡的檔案」,
第三段
commit-split --path指向工作樹,兩段的步驟編號各往後移一號。AGENTS.md:補上工作樹的集中位置,以及.git/worktrees/這條 git 自己的例外。test/worktree-path.test.js(表格驅動)、test/timer.test.js為新增,test/branch-prep.test.js改寫,claim與sdlc-feat-assets跟著調整。設計重點
為什麼工作樹是一律的,不是「有衝突才用」。 原地切分支有三種損耗:未提交的變更
擋路、建置產物跨分支混淆,以及 agent 讀到不屬於它那顆工作包的程式碼。前兩種人會
當場發現,第三種不會——agent 是非同步的,它可能在分支已經被切走之後才去讀檔,
而且不會察覺,產出看起來完全合理,只是接錯了上下文。
為什麼一定要
--no-track。 這不是理論問題:開這棵工作樹時 git 就自動把 upstream設成了
origin/master。此刻遠端還沒有這支新分支,upstream 只會落在來源分支上,之後
git pull會把來源分支的提交拉進來。upstream 留給第一次push -u自然建立。為什麼要回滾。 實測
git worktree add失敗時仍會把新分支留下來。那是最難查的半成品:下一次重跑會走到「目標分支已存在」那條路,起點從此不再是遠端的來源分支。
測試以「家目錄被一個檔案擋住」造出這個情境,並反向驗證過——拿掉回滾就紅。
為什麼路徑取雜湊而不是把斜線攤平。 攤平會讓
feat/a-b/main與feat/a/b/main撞成同一個目錄,而現行的分支命名規則恰好讓這種形狀有機會出現。
為什麼起錶要後移。 工作樹建立失敗會中止整個領取。錶要是照舊在領取那一步就起,
使用者會被計一段什麼都沒做的時間,而工時要準正是
/sdlc-report的立足點。工作區不乾淨不再是阻擋條件。
DIRTY_WORKTREE整條拿掉——讓未提交的變更不再擋路,本來就是工作樹要解決的事。
解決的問題
同時持有多顆工作包的人不必再為了換一顆而先處理未提交的變更;每顆工作包有自己的
依賴與建置產物;agent 無論什麼時候讀檔都只讀得到它那顆工作包的程式碼。
另外,被碼錶擋下來時會明說停錶不會動到既有的工作樹(#38 使用者故事第 30 條)——
以為停錶等於放棄工作包的人會寧可不停,工時就記到別顆去了。
影響的功能
/sdlc-feat第一段,以及第二、三段的工作目錄——它們的--path與實作動作都改指工作樹。branch-prep的介面有兩處不相容的變更:新增必填--repo,以及不再有
DIRTY_WORKTREE。正本已同步,沒有其他呼叫端。#11驗收標準第 4 條(來源分支在遠端已存在時 pull 而非重建)依 #38 由本工作包取代,#11 本身不修改;第 5 條的分支命名規則不變。
測試結果
npm test633 pass / 0 fail(已 rebase 到含 #45 的 master 之上)。工作樹相關的測試都在臨時 git repo 上跑真的 git,以本機裸 repo 充當遠端,不需網路。
另外對真實 repo 試跑過一次:
🤖 Generated with Claude Code
同一份 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>dbce8dbd3ctod3ce364727