Files
sdlc/references/branch.md
T

137 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 分支規則 — SDLC 各階段動工前的分支確認
分析要讀對分支的程式碼,實作要寫進對的分支。兩件事都不能從「目前 checkout 的分支」推定使用者的意圖。
## 總則:一律以遠端為準,不吃本地分支
SDLC 各階段引用的參考分支與來源分支,**一律指遠端的 `origin/{branch}`**。本地分支不可作為基準。
理由:本地分支可能落後遠端、可能有沒推上去的 commit、也可能是別的工作留下的狀態。以本地為基準,分析會對著過時的程式碼做,實作會從錯誤的起點長出來,而且錯了不會有任何徵兆。
| 項目 | 規則 |
| --- | --- |
| 動作之前 | 先 `git fetch --prune origin`。**沒 fetch 就用 `origin/{branch}` 等於用快取**,遠端追蹤參照可能已經過時,連分支被刪掉都看不出來 |
| 列分支 | `git branch -r`,不看 `git branch` |
| 基準與比對 | 一律寫 `origin/{branch}`,例如 `git rev-list --count origin/master..origin/develop` |
| 建立 worktree | 從 `origin/{source-branch}` 建,不從本地同名分支建 |
| 判定預設分支 | `refs/remotes/origin/HEAD`(見〔判定遠端預設分支〕) |
**本地與遠端不一致時一律停下回報**,不自行 `pull`、不自行 `reset`、不自行切換。要不要同步是使用者的決定。
`origin` 以外的遠端名稱(例如 `upstream`):先問使用者以哪個遠端為準,不臆測。
### 來源分支在遠端找不到:停下回報,不退回預設分支
已指定的來源分支,`origin/{source-branch}` 在遠端不存在時,**回報並停止**。不改用 `develop`、不改用 `master`、不套用任何後備分支,也不自行建立同名分支。這條沒有例外。
理由:來源分支是那份分析的功能分支。悄悄退回 `develop`,單一工作包就會越過自己的功能分支直接進 `develop`,而且沒有任何徵兆看得出來。
**這是停止條件,不是後備條件。**〔判定遠端預設分支〕那節的後備順序只用在「還沒有指定分支、要挑一條基準」的情況;指定過的來源分支找不到,一律套用本節。
## 先確認,再動工
| 階段 | 要確認的分支 | 確認時機 |
| --- | --- | --- |
| `analyze` | **來源分支**——哪條分支的程式碼算現況 | 讀任何程式碼之前 |
| `implement` | **來源分支**——worktree 的基準,也是每個工作包的 PR 目標 | 寫任何檔案之前 |
確認方式:
1. 先 `git fetch --prune origin`,再把事實攤開:目前分支、工作區是否乾淨、遠端有哪些分支(`git branch -r`,不看本地)、下節判定出的遠端預設分支。
2. 依 `jsc-ask:ask` 的決策樹詢問,每個選項都要標明影響範圍。分析錯分支會產出對不上程式碼的工作包;來源分支錯了則 worktree 從錯的起點長出來,PR 也會開到錯的地方。
3. 把確認結果寫進產出(分析頁的來源分支欄),後續步驟一律沿用同一個答案,不再自行改判。
4. 使用者沒回答就**不預設** `develop`/`master`;預設值只在使用者明確同意後才成立。
## 判定遠端預設分支
1. 先用 `git symbolic-ref --quiet refs/remotes/origin/HEAD`,結果形如 `refs/remotes/origin/<預設分支>`。
2. 取不到時退而用 `git remote show origin`,找輸出中的 `HEAD branch:` 那行。
3. 兩者都取不到 → 回報並停止,**不臆測** `master`/`main`。
還沒有指定分支、需要挑一條基準或後備分支時(例如維護階段要落腳的分支):`origin/develop` 存在就用 `develop`;否則 `origin/master` 存在就用 `master`;兩者皆無則回報並停止。
這條後備順序**不適用於已指定的來源分支**。指定過的來源分支在遠端找不到,一律回報並停止,見總則的〔來源分支在遠端找不到〕。
## 只讀階段不動工作區
`analyze` 是 logic-only 階段:
- **工作目錄的 HEAD 必須與 `origin/{source-branch}` 指向同一個 commit**。落後、超前或分歧都代表讀到的不是遠端現況——停下來回報差距(`git rev-list --left-right --count origin/{source-branch}...HEAD`),請使用者自己處理。
- 需要換分支才能讀到正確現況時,**停下來請使用者自己切換**。
- 不代為 `switch`、不 `stash`、不動工作區、不建分支。
## 實作階段的分支選擇
實作在 worktree 內進行,工作分支從 `origin/{source-branch}` 長出來,做完再 PR 回同一條來源分支。
- **一個工作包一個 PR**,目標一律是 `origin/{source-branch}`。不把兩個完成的工作包併成一個 PR,也不把完成的工作包留到下一包一起送——拆工作包的意義就是各自能獨立審查。
- 呼叫 `jsc-git:pr` 時**必須明確帶入來源分支當 base**。該 skill 在沒收到 base 時會自行退回 `develop`,那會讓單一工作包越過它所屬的功能分支直接進 develop。
- 來源分支整條後續要進 `develop` 時,那是另一個獨立的 PR,不在本階段範圍。
- 新分支名稱要可讀且不覆蓋既有分支;本地或遠端已存在同名分支時,換一個時間戳或短 hash。
- 切換或建立分支屬不可忽略的狀態變更,要在輸出中講清楚原因與結果分支名稱。
## 實作一律在 worktree 內進行
`implement` 動任何程式碼之前,先 `git fetch --prune origin`,再從**分析頁記錄的來源分支的遠端版本**(`origin/{source-branch}`)建立 git worktree。所有修改都在 worktree 內,主工作目錄的分支與工作區完全不動。
### 路徑
```
{cwd}/.worktree/{analysis-HASH}/{repo}
```
- `{analysis-HASH}`:`ANALYZE_{HASH}` 的 HASH 部分,不含 `ANALYZE_` 前綴。
- `{repo}`:存取庫名稱,不含 owner(`HP/WebService.Buy` → `WebService.Buy`)。不同 owner 的同名存取庫同時出現時,才改用 `{owner}-{repo}` 避免蓋掉,並在輸出中說明。
- 一份分析涉及多個存取庫時,每個存取庫各一個 worktree,並列在同一個 HASH 目錄下。
### 建立前先問分支怎麼處理
依 `jsc-ask:ask` 的決策樹詢問,每個選項標明影響範圍。固定兩個選項:
| 選項 | 指令 | 影響 |
| --- | --- | --- |
| 以來源分支為基準開新工作分支 | `git worktree add -b {work-branch} {path} "origin/{source-branch}"` | commit 落在新分支,來源分支不動 |
| 直接簽出來源分支 | `git worktree add --track -b {source-branch} {path} "origin/{source-branch}"` | 建立追蹤遠端的本地分支再簽出;本地已存在同名分支時指令會失敗,此時先確認它與遠端一致才可改用 `git worktree add {path} {source-branch}`,不一致就停下回報 |
**不得自行預設**,也不得跳過詢問。
### 建立時的鐵則
- **分支名與路徑一律加引號**:既有的來源分支可能含空白、多層斜線或非 ASCII 字元(例如 `feat/addr-lookup/query/full/P2` 這種多層命名,或更舊的分支帶空白),不加引號會被切斷。
- **jsc 自己開的分支只用 ASCII**(`a-z0-9` 與 `/`、`-`)。中文標題先翻譯成英文短語,再 slug 化當分支名。
- 找不到該存取庫的本地 clone → **停下來問使用者路徑**,不自行 clone、不臆測位置。
- 目標路徑已存在 → 不覆蓋。先確認它是不是同一份工作的 worktree(`git worktree list`),是就沿用,不是就回報並停止。
- 把 `.worktree/` 加進該存取庫的 `.git/info/exclude`(不動使用者的 `.gitignore`,那是專案共用檔)。
- 建立後在輸出中明確列出:worktree 路徑、簽出的分支、來源分支,以及 `origin/{source-branch}` 當下的 commit sha——那是這次實作的起點,要能事後追。
### 移除時機
**PR 合併之後才移除**:`git worktree remove {path}`,接著 `git worktree prune`。
不是 PR 建立後就移除——審查留言可能要求修改,而修改要回到同一個 worktree、推到同一條工作分支(PR 會自己更新,不開第二個 PR)。先移除就得為了改一行重建整個 worktree。
- PR 尚未合併 → 保留。PR 建立失敗 → 保留,讓使用者接手處理。
- worktree 內還有未提交變更時**不移除**,回報並停止——那些變更沒有進 PR,移除等於丟掉。
- 同一份分析的多個 worktree 全部移除後,若 `.worktree/{HASH}/` 已空就一併刪掉那層目錄。
### PR 未合併就不開下一個工作包
一個工作包的 PR 還沒合併,就不得開始下一包。修正一律回原 worktree、推同一條工作分支,**不為同一包開第二個 PR**。
這條規則在程式層強制,不靠內文自律,分工是兩層:
| 層 | 誰執行 | 做什麼 |
| --- | --- | --- |
| 工具閘門 | `tools/wp-gate.sh` | 唯一去問 Gitea 的一方,也是唯一有權解鎖的一方。`check` 查合併狀態、印出全部留言、印 `latest=` 時間戳;`lock` 在 PR 開好後記下未結清。結束碼 0 放行、1 擋住、2 用法錯誤、3 查不到(查不到就擋,不放行) |
| hook 提醒 | `jsc-hooks` 的 `sdlc-gate.sh wp-check` | 只讀 `$JSC_HOME/wp/` 的狀態檔,不打網路。`prompt` 模式注入提醒但**不擋提示**;`skill` 模式擋掉 `plan`、`analyze`、`maintain`,放行 `implement`——擋住的是「跳去做別的階段」,不是「回來把這一包做完」 |
狀態檔不綁 session:PR 沒合併就是沒合併,開新對話照樣擋。
查驗程序的細節(何時帶 `--since`、留言逐筆的處理結果、修不動的留言怎麼辦、時間戳寫回哪裡)由 `skills/implement/SKILL.md` 步驟 4 擁有,本檔不重複。
## 不破壞既有工作
- 工作區有未提交變更時,先提醒使用者 commit 或備份,**絕不**強制丟棄。
- 未提交變更導致切換分支或 pull 失敗 → 停止並回報,請使用者處理。
- **絕不** `reset --hard`/`checkout -f`/`clean`。