base-branch.sh、pick-type.sh、slugify.sh 三支在外掛根目錄的 tools/ 底下, 但技能本文寫成不帶前綴的 tools/…,照字面解會落到 skills/pr/tools/,那個 路徑不存在、結束碼 127。同一份文件引用別的外掛時本來就寫 jsc-gitea/tools/ 與 jsc-hooks/tools/,只有自家的掉了前綴,十三處全部補齊。 這個坑比看起來嚴重。第 4 步明寫不准自己挑基底分支,而跑不動推導腳本的 代理人離「自己挑一個」只差一步——路徑打錯就變成 PR 開在錯的階梯上,而且 PR 會照樣開出來,不會有任何錯誤。開頭補一段講明每一條腳本路徑相對於誰解, 以及這個 127 為什麼不只是跑不動而已。 三份 manifest 版號 0.1.5 升到 0.1.6。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 KiB
name, description
| name | description |
|---|---|
| pr | Commit all changes via jsc-git:commit, name a ladder-shaped target branch, derive the base with jsc-git/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
The ladder rules live in one place only: section 「PR 分支階梯」 of jsc-meta/references/guidelines.md. jsc-git/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.
Every script path in this file starts with its plugin's own directory name — jsc-git/tools/… for this plugin's, jsc-gitea/tools/… and jsc-hooks/tools/… for the others — and is resolved against the tool root the caller supplies, never against this skill's own directory. These three scripts sit at the plugin root, not under skills/pr/, so a bare tools/base-branch.sh resolves to a path that does not exist and exits 127. That failure is worse than it looks: step 4 says never hand-pick a base, and an agent that cannot run the deriver is one step away from picking one anyway, which turns a wrong path into a PR opened against the wrong rung.
Steps
-
Validate the caller's base first. A caller may pass a base in (
jsc-sdlc:implementpasses 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 runjsc-git/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:askrules 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
--derivemode 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.
-
Call
jsc-git:committo commit every file change. Tell it thatjsc-git:pris the caller, so it skips its own open-PR lookup — step 6 here is the only such lookup in this chain. Done whengit status --porcelainprints nothing. -
Build the target branch name in ladder shape:
- Pipe the subjects of step 2's commits into
jsc-git/tools/pick-type.sh(git log --format=%s {range} | jsc-git/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. - Summarize one title from all commit messages, as a single Traditional Chinese sentence saying what this PR does. This sentence is the PR title steps 7 and 8 use; step 3.3 only borrows its meaning to build the ASCII slug. Done when one title line covers every commit in the range and reads as one Traditional Chinese sentence.
- Translate the feature and the title into short English phrases, then build the name with
jsc-git/tools/slugify.sh.fixtakes one call:jsc-git/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:jsc-git/tools/slugify.sh feat {feature phrase}→feat/order-export, thenjsc-git/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.
- 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.
- Pipe the subjects of step 2's commits into
-
Resolve the base branch:
- Run
jsc-git/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
--deriveflag 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 thejsc-ask:askrules 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.
- Run
-
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-pickthem when they landed on another branch), then rungit push -u origin {target branch}. Done whengit branch --show-currentprints the target branch,git log --oneline origin/{base branch}..HEADlists exactly step 2's commits, andgit ls-remote --heads originshows the target branch's ref. -
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 callspr-get. On exit 0 the script prints line 1number<TAB>{PR number}, line 2title<TAB>{title}, line 3base<TAB>{base}, line 4 the markerbody, 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.
-
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}. Ask the user to confirm before this call runs.- Title = step 3.2's summary: one Traditional Chinese sentence saying what this PR does. Never pass the branch name as the title. A branch name and a title carry different jobs — the branch name is a machine-readable ASCII slug that the ladder and
jsc-git/tools/base-branch.shparse, while the title is the one line a human reads in the review list, where a column of long slugs shows who touched the repo but never what changed. The STE100 output rule injsc-meta/references/ste100.mdnames PR titles and descriptions explicitly, and a hook enforces it; binding the title to the branch name is what put the two rules in conflict, so the title gives way to the language rule and the branch name stays ASCII per rule 4. - 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). - 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, whosepr-createnever 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.- 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, the title sent to
pr-createwas the Traditional Chinese sentence and not the branch name, and the prerequisite is settled —pr-dependprinted itsOKline, or the PR title starts withWIP: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. - Title = step 3.2's summary: one Traditional Chinese sentence saying what this PR does. Never pass the branch name as the title. A branch name and a title carry different jobs — the branch name is a machine-readable ASCII slug that the ladder and
-
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.
- Title against what this PR now contains. Read the existing title and ask one question only: does it still describe the PR's current content, now that step 2's commits are in? Yes → leave it alone. No → draft a replacement, again one Traditional Chinese sentence saying what this PR does. The branch name never enters this comparison. Comparing them would rewrite a good Traditional Chinese title back into an ASCII slug on every run and notify every reviewer each time, which is the opposite of what a title is for: the branch name is the machine's handle on the ladder, the title is what a human reads in the review list.
- Description against a freshly drafted description from
templates/pr-description.md. - 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. Ask the user to confirm before this call runs. 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-dependwith the exit code branching of step 7. Ask the user to confirm before this call runs. - 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, the title verdict states which content the title was judged against rather than any branch name, and the number of API calls made equals the number of items that differed.
-
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 injsc-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,revieworinline) → 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. -
Record how this run ended, as the very last thing this skill does:
jsc-hooks/tools/report-status.sh skill-end jsc-git:pr {status} {exit} "{detail}"Resolve that path the way this file already resolves
jsc-gitea/tools/gitea.sh— the sibling plugin directory, no separate lookup rule for this one call. A missing script is not a failure here: skip this step in silence and let the run end as it stands. The script swallows its own write errors and exits 0 even then, so nothing branches on its code either. A PR that is open stays open whether or not the run could be recorded.status This skill's case okA PR URL is held, the prerequisite is settled by a pr-dependexit 0 or by a description that names none, and every handled comment holds a reply link. Calibrating an existing PR and changing none of the three items isokas well: silence is the correct outcome thereblockedThe ladder refused before anything was pushed: base-branch.shexit 3 (origin unreachable), exit 4 (the caller base is not on origin), exit 7 (no unique legal base), exit 8 (the derived base is missing on origin) or exit 9 (the auto-create failed). No branch reached origin and no PR was openeddegradedThe PR is open but part of the close-out did not land: pr-dependexit 4 sent the run to theWIP:fallback, so the dependency is not on the PR, or a handled comment ended with a recorded failure instead of a reply linkfailedThe run got partway and then git or the API refused: pick-type.shexit 3 (commits exist but no subject carries a ladder type), a push that failed,pr-createexit 4, or a non-zeropr-editthat left the old title and description in placeabortedpick-type.shexit 2 — step 2 produced no commit, so there is nothing to open a PR for. That is a run which correctly stopped, not a run that failed and not a run that succeeded; recording it as anything else is what made a round with no change look identical to a round that shipped eight PRs. The user declining the confirmation beforepr-create,pr-editorpr-dependisabortedtoo{exit}is the exit code of whatever decided the status,0forok— so the no-change round above carriesabortedwith2.{detail}is one short line well under 200 characters: counts and exit codes plus which of the three calibration items moved, never the PR title, the PR number, branch names, or personal data.Done when the command has run, or the script was absent and this step was skipped.
Rules
- No template section may stay empty: fill the literal 「無」 when there is no plan page, analyze page, or prerequisite PR.
- Read the
jsc-sdlcplan page and analyze page throughjsc-gitea:wiki, which resolvesJSC_WIKI_REPO_PLANfor the plan page andJSC_WIKI_REPO_ANALYZEfor the analyze page from the inherited environment, falls back toJSC_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. - 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.
- Branch names are ASCII only (
a-z0-9,/,-). Traditional Chinese phrases go throughjsc-git/tools/slugify.shfirst;jsc-git/tools/base-branch.sh --deriverejects anything else. PR titles and descriptions take the opposite rule: they stay Traditional Chinese perjsc-meta/references/ste100.md. The two never copy each other.