fix(skills): 補齊失敗分支並解開分組步驟的循環相依
兩支技能的步驟有幾處寫不完整。出錯時流程會走偏,步驟之間也互相卡住。 - 建立 PR、修改 PR、回覆留言、查詢開啟中的 PR,四處原本只寫成功路徑。API 失敗會被當成「沒有開啟中的 PR」,於是在同一支分支上重開一支。現在四處都補上失敗分支。 - 推導基底的腳本結束碼原本只分流三個,其餘落空。現在每個結束碼都有處置與下一步。 - 交叉引用指到收尾步驟,改指回真正做校準的那一步。 - 分組原本被拉進平行區塊,但分組要等盤點的檔案清單,盤點的完成條件又要等分組結果。現在只有註解掃描與盤點平行,分組排在盤點之後。 - 認可與開 PR 原本各查一次開啟中的 PR。改成共用單次查詢,並拿掉認可回呼開 PR 的遞迴。 - 呼叫方傳入的基底改在最前面驗證。基底不合法就當場擋下,不必等命名做完。 - 重複的階梯表從技能內文刪掉,改指向指引正本。
This commit is contained in:
+65
-36
@@ -5,53 +5,82 @@ description: Commit all changes via jsc-git:commit, name a ladder-shaped target
|
||||
|
||||
# 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.
|
||||
The ladder rules live in one place only: section 「PR 分支階梯」 of `jsc-meta/references/guidelines.md`. `tools/base-branch.sh --derive` is the running implementation of that section. Read the rung a branch belongs to there; never hand-pick a base, and never restate the ladder table in this file.
|
||||
|
||||
## 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.
|
||||
1. **Validate the caller's base first.** A caller may pass a base in (`jsc-sdlc:implement` passes the analysis page's source branch). Whether that base is legal depends on origin alone, not on the target branch name, so settle it before any naming work. Skip the whole step when no caller base was passed, and record "no caller base" for step 4. Otherwise run `tools/base-branch.sh {caller base}` and branch on its exit code:
|
||||
- 0 → keep the printed name; step 4 cross-checks it against the derived base.
|
||||
- 2 (too many arguments) → the base arrived as several words. Quote it as one argument and rerun.
|
||||
- 3 (cannot reach origin) → stop and report that origin is unreachable. Nothing downstream works without the remote.
|
||||
- 4 (caller base not on origin) → stop, report the caller base, and ask the user per the `jsc-ask:ask` rules which existing branch was meant. Never substitute another branch.
|
||||
- 5 (develop, main and master all missing on origin) → the script only reaches this code with no argument, so the caller base was dropped on the way in. Stop and report that the argument never reached the script.
|
||||
- 6 (empty string) → the caller's base variable is unset. Stop and ask the caller for the real branch name. Never rerun with the argument removed: that silently falls back to `develop`.
|
||||
- 7, 8, 9 → these belong to `--derive` mode only. Seeing one means the wrong mode ran. Stop and report which command line was used.
|
||||
- Done when the script printed one branch name, or the run is recorded as having no caller base.
|
||||
2. Call `jsc-git:commit` to commit every file change. Tell it that `jsc-git:pr` is the caller, so it skips its own open-PR lookup — step 6 here is the only such lookup in this chain. Done when `git status --porcelain` prints nothing.
|
||||
3. Build the target branch name in ladder shape:
|
||||
1. Pipe the subjects of step 2's commits into `tools/pick-type.sh` (`git log --format=%s {range} | tools/pick-type.sh`), which owns the type priority order. Exit 0 → take the printed type. Exit 2 (no input) → step 2 produced no commits, so there is nothing to open a PR for; stop and report. Exit 3 (no ladder type in the input) → stop, report the subjects, and correct them to `{type}({scope}): {message}` before retrying. Done when the script printed exactly one type.
|
||||
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}`.
|
||||
- The name is the only input step 6's lookup and the description draft need. Start both the moment this step ends, and run them alongside steps 4 and 5; neither waits for the push.
|
||||
4. Resolve the base branch:
|
||||
- Run `tools/base-branch.sh --derive {target branch}`. Exit 0 → the script printed one branch name.
|
||||
- Exit 2 (too many arguments) → the branch name arrived as several words. Quote it as one argument and rerun.
|
||||
- Exit 3 (cannot reach origin) → stop and report that origin is unreachable.
|
||||
- Exit 4, 5 or 6 → these belong to caller mode. Seeing one means the `--derive` flag was dropped. Rerun with the flag.
|
||||
- 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`.
|
||||
- Step 1 held a caller base → compare the two names. Same → use it. Different → the pair skips a rung, so stop, report the caller base, the derived base, and the ladder row that applies per `jsc-meta/references/guidelines.md`, 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.
|
||||
- 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 9 has to report it.
|
||||
- Done when one base name is held, and the caller base either matched it or the user settled the mismatch.
|
||||
5. Put the branch on origin, in one pass: run `git checkout -b {target branch} origin/{base branch}` so the branch starts at the remote base, bring step 2's commits onto it (`git cherry-pick` them when they landed on another branch), then run `git push -u origin {target branch}`. Done when `git branch --show-current` prints the target branch, `git log --oneline origin/{base branch}..HEAD` lists exactly step 2's commits, and `git ls-remote --heads origin` shows the target branch's ref.
|
||||
6. Look up the open PR of the target branch: `jsc-gitea/tools/gitea.sh pr-of-branch {owner}/{repo} {target branch}`. Launch it as soon as step 3 named the branch. This is the only open-PR lookup in the whole chain: step 8 reuses this output and never calls `pr-get`. On exit 0 the script prints line 1 `number<TAB>{PR number}`, line 2 `title<TAB>{title}`, line 3 `base<TAB>{base}`, line 4 the marker `body`, and line 5 onward the description.
|
||||
- Exit 0 → hold the PR number, title, base and body; skip step 7 and go to step 8, because the PR exists and only needs calibrating.
|
||||
- Exit 3 (no open PR on that branch) → go to step 7 and create one.
|
||||
- Exit 2 (usage error) → the `{owner}/{repo}` or the branch argument is missing or malformed. Correct the arguments and rerun.
|
||||
- Exit 4 (API call failed) → stop and report the failure. Never read a failed call as "no open PR": that opens a second PR on a branch that already has one.
|
||||
- Any other non-zero → treat it as exit 4 and stop.
|
||||
- Done when either the PR number plus its title, base and body are held, or exit 3 confirmed the branch has no open PR.
|
||||
7. Create the PR, then hang its prerequisite on it. Run `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
|
||||
- Exit 0 → the command prints a PR URL; keep it.
|
||||
- Exit 2 (description file not found) → write the description file, then rerun.
|
||||
- Exit 1 → an older `gitea.sh`, whose `pr-create` never checks the description file and lets the read throw instead. Same cause and same handling as exit 2: correct the description file path, then rerun.
|
||||
- Exit 4 (the API rejected the request) → report the script's message and stop. Never retry against a different base.
|
||||
- Any other non-zero → treat it as exit 4 and stop.
|
||||
|
||||
**Prerequisite PR blocking**: with the PR URL in hand, and only 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. If this run follows PR comment fixes and the caller supplied handled comment ids, reply to each comment now with `jsc-gitea/tools/gitea.sh comment-reply`. Rules: `jsc-meta/references/pr-report.md`. Done when every handled comment has a reply URL, or a reported reply failure reason.
|
||||
11. Report the PR with the table format in `jsc-meta/references/pr-report.md`, then report 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 includes the PR table columns `{owner}/{repo}`, PR number, PR URL and PR summary, and names all branch and calibration details.
|
||||
to add the dependency. Gitea then blocks merging until the prerequisite PR closes.
|
||||
- Exit 0 → the command prints `OK {owner}/{repo}#{number} depends on ...`.
|
||||
- Exit 2 (usage error) → an argument is missing or malformed. Correct it and rerun.
|
||||
- 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.
|
||||
- Any other non-zero → treat it as exit 4 and degrade the same way.
|
||||
|
||||
Done when a PR URL is held **and** the prerequisite is settled — `pr-depend` printed its `OK` line, or the PR title starts with `WIP:` and the description names the prerequisite PR, or the description names no prerequisite and that is stated. A PR created here goes straight to step 9.
|
||||
8. **Calibrate an existing PR** (step 6 returned a PR number): compare three items against what this run produced, using the title, base and description step 6 already returned. Send an API call only for the items 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. Exit 0 → updated. Exit 2 (description file not found) → write the file and rerun. Exit 4 or any other non-zero → report the script's message and stop, and say plainly that the PR still holds its old title and description.
|
||||
- The dependency differs → run `pr-depend` with the exit code branching of step 7.
|
||||
- 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.
|
||||
9. Reply to the handled comments, then report the run.
|
||||
|
||||
If this run follows PR comment fixes and the caller supplied handled comment ids, reply to each comment first with `jsc-gitea/tools/gitea.sh comment-reply`. The reply content and the comment type mapping live in `jsc-meta/references/pr-report.md`; the exit codes belong here. Run one call per handled comment and branch on each call's own exit code:
|
||||
- Exit 0 → the command prints the reply link. Keep it against that comment id.
|
||||
- Exit 2 (the reply file is missing, or the kind is not `issue`, `review` or `inline`) → correct the reply file path or the kind, then rerun that one call.
|
||||
- Exit 4 (the API call failed), or any other non-zero → record that comment id with the reason it failed and carry on with the remaining comments. One failed reply never ends the round.
|
||||
|
||||
Then report the PR with the table format in `jsc-meta/references/pr-report.md`, followed by the base branch, the target branch, which of the three calibration items were updated, the feature trunk the script auto-created when step 4 printed that line, and every comment that got no reply.
|
||||
|
||||
Done when every handled comment holds either a reply link or a recorded failure reason, and the report includes the PR table columns `{owner}/{repo}`, PR number, PR URL and PR summary, names all branch and calibration details, and names the comments that got no reply.
|
||||
|
||||
## 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.
|
||||
3. Branch title summarization (step 3.2) and description drafting MUST run as a sub agent. The description sub agent starts right after step 3 and runs alongside steps 4 and 5, so steps 7 and 8 already hold a draft.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user