Files
git/skills/pr/SKILL.md
T
jiantw83 82fb8077fc feat(pr): 目標分支改走階梯,既有 PR 改為校準不重開
What:`pr` 技能新增「PR ladder」一節與階梯形狀的分支命名(`fix` 叫一次 `slugify.sh`,其餘型別叫兩次組出 `{類型}/{功能}/{子功能}`),base 一律由 `base-branch.sh --derive` 推導;新增步驟 6 查目標分支有沒有開啟中的 PR,有就跳到新的步驟 9 比對標題、描述、前置 PR 依賴三項,只有不一樣的那幾項才送 API。`commit` 技能新增步驟 5:認可完成後,目前分支已有 PR 就交給 `pr` 校準。

Why:越級開 PR 與「同一條分支重開第二支 PR」是同一個病灶的兩面——base 與 PR 現況都靠人記。改成先推導、先比對之後,base 由腳本決定,PR 只在真的有差時才更新。

How:呼叫方傳入的 base 仍然照收,但要與推導結果一致;不一致就當成越級擋下,說明正確階梯再問使用者,不會悄悄改目標。校準走 `jsc-gitea` 的 `pr-get` 讀現況、`pr-edit` 一次帶標題與描述、`pr-depend` 補依賴;三項都相同就什麼都不做——每一次多餘的編修都會通知所有審查者,安靜才是對的結果。

Who:`jsc-git:pr` 與 `jsc-git:commit` 兩支技能,以及呼叫它們的 `jsc-sdlc:implement`、`jsc-sdlc:maintain` 與 `jsc-meta` 各技能。
2026-08-27 11:20:28 +08:00

57 lines
7.3 KiB
Markdown

---
name: pr
description: Commit all changes via jsc-git:commit, name a ladder-shaped target branch, derive the base with tools/base-branch.sh (sub-feature → feat/{feature}/main → develop → master; fix → develop → master, no level skipping), push, then open a Gitea PR with the templated description. When the branch already has an open PR, compare its title, description, and prerequisite dependency, and update only the items that differ. Use when work is ready for review, or when an open PR needs re-syncing after new commits; not for plain commits.
---
# pr — create a Push Request
## PR ladder
Every PR targets the next rung up. Skipping a rung is forbidden.
| Commit type | Ladder |
| --- | --- |
| feat, docs, style, refactor, perf, test, chore, revert | `{type}/{feature}/{sub-feature}` → `{type}/{feature}/main` → `develop` → `master` |
| fix | `fix/{change}` → `develop` → `master` |
`{sub-feature}` may hold several levels (`feat/a/b/c`); the base is always the branch name with its last segment replaced by `main`. `tools/base-branch.sh --derive` owns this derivation — never hand-pick a base.
## Steps
1. Call `jsc-git:commit` to commit every file change first. Done when `git status --porcelain` prints nothing.
2. Build the target branch name in ladder shape:
1. Take the type with the highest priority across all commits: `revert > fix > feat > perf > refactor > test > docs > style > chore`. Done when exactly one type is picked.
2. Summarize one title from all commit messages. Done when one title line covers every commit in the range.
3. Translate the feature and the title into short English phrases, then build the name with `tools/slugify.sh`. `fix` takes one call: `tools/slugify.sh fix {change phrase}` → `fix/order-export-crash`. Every other type takes two calls, feeding the first result back in as the type: `tools/slugify.sh feat {feature phrase}` → `feat/order-export`, then `tools/slugify.sh feat/order-export {sub-feature phrase}` → `feat/order-export/report-filter`. Done when the name has the shape its ladder row requires; exit 2 (non-ASCII input) or exit 3 (empty slug) → re-translate into an English phrase and retry, never hand-build the branch name.
3. Resolve the base branch:
- Run `tools/base-branch.sh --derive {target branch}`. Done when the script prints one branch name.
- The caller passed a base in (`jsc-sdlc:implement` passes the analysis page's source branch): run `tools/base-branch.sh {caller base}` as well. Both print the same name → use it. They differ → the pair skips a rung, so stop, report the caller base, the derived base, and the ladder row that applies, and ask the user per the `jsc-ask:ask` rules whether to rename the branch or correct the caller base. Never silently retarget the PR.
- Exit 7 (no unique legal base), 8 (derived base missing on origin), or 9 (auto-create failed) → report the script's message and stop. Never fall back to `develop`.
- The feature trunk missing on origin is not an error: the script creates it from `develop`, pushes it, and prints 「已自動從 develop 建立功能主幹 {分支}」 to stderr. Capture that line; step 10 has to report it.
4. Run `git checkout -b {target branch} origin/{base branch}` so the branch starts at the remote base, then bring step 1's commits onto it (`git cherry-pick` them when they landed on another branch). Done when `git branch --show-current` prints the target branch and `git log --oneline origin/{base branch}..HEAD` lists exactly step 1's commits.
5. Run `git push -u origin {target branch}`. Done when `git ls-remote --heads origin` shows the target branch's ref.
6. Find out whether the target branch already has an open PR: `jsc-gitea/tools/gitea.sh api GET /repos/{owner}/{repo}/pulls?state=open` and match `head.ref` against the target branch. Done when you hold either one PR number or the fact that there is none; a PR number → skip step 7 and step 8 and go to step 9, because the PR already exists and only needs calibrating.
7. Create the PR: `jsc-gitea/tools/gitea.sh pr-create {owner}/{repo} {target branch} {base branch} "{branch name}" {description file}`.
- Title = branch name.
- Write the description into a temp file first, using `templates/pr-description.md` (a Traditional Chinese template; the generated description stays in Traditional Chinese per the STE100 output rule).
- Done when the command prints a PR URL; no URL → report the error and stop.
8. **Prerequisite PR blocking**: when the description lists a prerequisite Push Request, run
`jsc-gitea/tools/gitea.sh pr-depend {owner}/{repo} {this PR number} {prerequisite owner}/{repo} {prerequisite PR number}`
to add the dependency. Gitea then blocks merging until the prerequisite PR closes. On exit 4 (the dependency API call failed) degrade: prefix the PR title with `WIP:`, which Gitea blocks merging on natively, and record in the PR description that the prefix comes off once the prerequisite PR closes. Done when `pr-depend` printed `OK {owner}/{repo}#{number} depends on ...`, or the PR title starts with `WIP:` and the description names the prerequisite PR. A PR created here goes straight to step 10.
9. **Calibrate an existing PR** (step 6 found a PR number): read it once with `jsc-gitea/tools/gitea.sh pr-get {owner}/{repo} {pr number}` — line 1 is `title<TAB>{title}`, line 2 is `base<TAB>{base}`, line 3 is the marker `body`, and line 4 onward is the description. Compare three items against what this run produced, and send an API call only for the ones that differ:
1. Title against the target branch name.
2. Description against a freshly drafted description from `templates/pr-description.md`.
3. Prerequisite PR dependency against the one the description names.
- Title or description differs → write the new description to a temp file and run `jsc-gitea/tools/gitea.sh pr-edit {owner}/{repo} {pr number} "{title}" {description file}`, which carries both items in one call.
- The dependency differs → run `pr-depend` as in step 8.
- All three match → change nothing. Every needless edit notifies every reviewer, so silence is the correct outcome here.
- Done when each of the three items is reported as either matched or updated, and the number of API calls made equals the number of items that differed.
10. Report the PR URL, the base branch, the target branch, which of the three calibration items were updated, and the feature trunk the script auto-created when step 3 printed that line. Done when the report names all of them.
## Rules
1. No template section may stay empty: fill the literal 「無」 when there is no plan page, analyze page, or prerequisite PR.
2. Read the `jsc-sdlc` plan page and analyze page through `jsc-gitea:wiki`, which resolves `JSC_WIKI_REPO_PLAN` for the plan page and `JSC_WIKI_REPO_ANALYZE` for the analyze page from the inherited environment, falls back to `JSC_WIKI_REPO`, and asks only when neither is set. Each page type reads its own variable only; a page type never borrows another type's variable. Take both links from the pages read this way; the analyze link must point at the work package heading anchor.
3. Branch title summarization (step 2.2) and description drafting MUST run as a sub agent.
4. Branch names are ASCII only (`a-z0-9`, `/`, `-`). Traditional Chinese phrases go through `tools/slugify.sh` first; `tools/base-branch.sh --derive` rejects anything else.