From bf68ee34368ae6ef1aff79c81e678682495ce2f6 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 2 Sep 2026 16:01:14 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(=E7=8B=80=E6=85=8B=E5=9B=9E=E5=A0=B1):?= =?UTF-8?q?=20=E6=94=B6=E5=B0=BE=E5=AF=AB=E4=B8=80=E7=AD=86=20skill-end=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就 中止的技能,在紀錄裡長得一模一樣。 start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾 步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在 原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。 status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜 跳過,回報失敗一律不改變技能自己的結論。 --- references/behaviors.md | 12 ++++++------ skills/commit/SKILL.md | 17 +++++++++++++++++ skills/pr/SKILL.md | 17 +++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/references/behaviors.md b/references/behaviors.md index a4ba0ca..b4a5645 100644 --- a/references/behaviors.md +++ b/references/behaviors.md @@ -7,17 +7,17 @@ | 項目 | 內容 | | --- | --- | | 觸發時機 | 工作區有待提交的檔案變更時叫用。`jsc-git:pr` 的步驟 2 也會叫用它。要推送或開 PR 時不叫用這一支,改叫 `jsc-git:pr`。 | -| 關鍵步驟 | 用 `git status --porcelain` 盤點所有變更路徑、同時跑 `jsc-hooks/hooks/comment-scope.sh sweep` 掃過工作區的註解、依「同型別加同需求或功能」把路徑分組、每組一次 `git add` 加一次 `git commit`、訊息寫成 `{type}({scope}): {message}`、最後校準本分支既有的 PR。 | +| 關鍵步驟 | 用 `git status --porcelain` 盤點所有變更路徑、同時跑 `jsc-hooks/hooks/comment-scope.sh sweep` 掃過工作區的註解、依「同型別加同需求或功能」把路徑分組、每組一次 `git add` 加一次 `git commit`、訊息寫成 `{type}({scope}): {message}`、校準本分支既有的 PR、最後跑 `jsc-hooks/tools/report-status.sh skill-end jsc-git:commit {status} {結束碼} {detail}` 記下本輪結果,腳本不在這台機器上就安靜跳過。 | | 外部呼叫 | `git status --porcelain`、`git add`、`git commit`、`git diff`、`jsc-hooks/hooks/comment-scope.sh sweep`、`jsc-gitea/tools/gitea.sh pr-of-branch`(僅獨立執行時)、`jsc-git:pr`(校準既有 PR)、`jsc-ask:ask`(問訊息風格)。分組與訊息草稿交給 sub agent 處理。 | -| 完成條件 | 註解掃描退出 0,或每一則剩餘警告都被判為誤判並說明理由,或腳本不在這台機器上並回報。`git status --porcelain` 印出空白。步驟 5 回報四種結果之一:因為 `jsc-git:pr` 是呼叫方而略過查詢、本分支沒有開啟中的 PR、`jsc-git:pr` 回報三個項目各自相符或已更新、查詢失敗並指名失敗原因。 | -| 可驗證跡象 | 本地 git 歷史多出一批 commit,`git log --oneline` 看得到,每一筆標題是 `{type}({scope}): {message}` 且訊息含繁體中文。工作區乾淨。註解掃描要求的修正直接改在原始碼檔案裡。獨立執行且本分支有開啟中的 PR 時,Gitea 上那條 PR 的標題、描述、前置依賴由 `jsc-git:pr` 更新。這一支不推送、不建立 PR、不寫 wiki 頁。 | +| 完成條件 | 註解掃描退出 0,或每一則剩餘警告都被判為誤判並說明理由,或腳本不在這台機器上並回報。`git status --porcelain` 印出空白。步驟 5 回報四種結果之一:因為 `jsc-git:pr` 是呼叫方而略過查詢、本分支沒有開啟中的 PR、`jsc-git:pr` 回報三個項目各自相符或已更新、查詢失敗並指名失敗原因。收尾一定要寫一筆 `skill-end` 狀態事件:每一組都提交完且工作區乾淨是 `ok`,`jsc-hooks` 的 Bash 閘門把提交擋在門外是 `blocked`,提交都進去了但既有 PR 沒校準成功是 `degraded`,`git add` 或 `git commit` 中途回非零、工作區還髒是 `failed`,盤點結果是空的、根本沒有變更可提交是 `aborted`。 | +| 可驗證跡象 | 本地 git 歷史多出一批 commit,`git log --oneline` 看得到,每一筆標題是 `{type}({scope}): {message}` 且訊息含繁體中文。工作區乾淨。註解掃描要求的修正直接改在原始碼檔案裡。獨立執行且本分支有開啟中的 PR 時,Gitea 上那條 PR 的標題、描述、前置依賴由 `jsc-git:pr` 更新。這一支不推送、不建立 PR、不寫 wiki 頁。跑完 `$JSC_HOME/usage/events.jsonl` 會多一筆 `{kind:skill,phase:end}` 事件,`name` 欄是 `jsc-git:commit`,`status` 與 `exit` 兩欄對得上上一列講的判準,沒有變更可提交那一輪看得到 `aborted`;`jsc-hooks` 不在這台機器上時沒有這一筆,技能本身照樣跑完。 | ## pr | 項目 | 內容 | | --- | --- | | 觸發時機 | 工作做完要送審時叫用。既有 PR 在新 commit 之後要重新同步時也叫用。只想提交不想推送時不叫用這一支,改叫 `jsc-git:commit`。 | -| 關鍵步驟 | 先用 `tools/base-branch.sh {呼叫方基底}` 驗證呼叫方傳進來的基底、叫 `jsc-git:commit` 提交全部變更、用 `tools/pick-type.sh` 從 commit 標題選出型別、用 `tools/slugify.sh` 組出階梯狀目標分支名、用 `tools/base-branch.sh --derive {目標分支}` 推導基底並和呼叫方基底比對、以 `git checkout -b {目標分支} origin/{基底分支}` 建分支並 `git push -u origin` 推上去、用 `gitea.sh pr-of-branch` 查一次開啟中的 PR、沒有就用 `gitea.sh pr-create` 開新 PR 並用 `gitea.sh pr-depend` 掛前置依賴、已經有就只更新標題、描述、前置依賴三項裡不同的那幾項、回覆已處理的 PR 意見、最後回報。PR 標題寫一句繁體中文摘要,說明這條 PR 做了什麼,不套用分支名;校準既有 PR 時只問標題還描述不描述得了目前的內容,不拿分支名比對。 | +| 關鍵步驟 | 先用 `tools/base-branch.sh {呼叫方基底}` 驗證呼叫方傳進來的基底、叫 `jsc-git:commit` 提交全部變更、用 `tools/pick-type.sh` 從 commit 標題選出型別、用 `tools/slugify.sh` 組出階梯狀目標分支名、用 `tools/base-branch.sh --derive {目標分支}` 推導基底並和呼叫方基底比對、以 `git checkout -b {目標分支} origin/{基底分支}` 建分支並 `git push -u origin` 推上去、用 `gitea.sh pr-of-branch` 查一次開啟中的 PR、沒有就用 `gitea.sh pr-create` 開新 PR 並用 `gitea.sh pr-depend` 掛前置依賴、已經有就只更新標題、描述、前置依賴三項裡不同的那幾項、回覆已處理的 PR 意見、回報,最後跑 `jsc-hooks/tools/report-status.sh skill-end jsc-git:pr {status} {結束碼} {detail}` 記下本輪結果,腳本不在這台機器上就安靜跳過。PR 標題寫一句繁體中文摘要,說明這條 PR 做了什麼,不套用分支名;校準既有 PR 時只問標題還描述不描述得了目前的內容,不拿分支名比對。 | | 外部呼叫 | `tools/base-branch.sh`、`tools/pick-type.sh`、`tools/slugify.sh`、`templates/pr-description.md`、`jsc-git:commit`、`jsc-gitea/tools/gitea.sh` 的 `pr-of-branch`、`pr-create`、`pr-edit`、`pr-depend`、`comment-reply`、`jsc-gitea:wiki`(讀 PLAN 頁與 ANALYZE 頁)、`jsc-ask:ask`(基底衝突與分支不存在時發問)、`jsc-meta/references/guidelines.md` 的「PR 分支階梯」、`jsc-meta/references/pr-report.md`、`git checkout`、`git cherry-pick`、`git push`、`git ls-remote`。分支標題摘要與描述草稿交給 sub agent 處理。 | -| 完成條件 | 握有一個 PR 網址,送出的標題是一句繁體中文摘要,不是分支名。前置依賴已經處理完:`pr-depend` 印出 `OK` 行,或 PR 標題冠上 `WIP:` 且描述指名前置 PR,或描述寫「無」前置 PR 並在回報裡說明。既有 PR 的三個校準項目各自回報為相符或已更新,標題那一項說明拿什麼內容去判定,送出的 API 呼叫數等於不同的項目數。每一則已處理的意見握有回覆連結或記下失敗理由。回報含 `{owner}/{repo}`、PR 編號、PR 網址、PR 摘要四欄,並指名基底分支、目標分支、更新過的校準項目、自動建立的功能主幹、沒回覆到的意見。 | -| 可驗證跡象 | origin 上多出目標分支的 ref,`git ls-remote --heads origin` 查得到。Gitea 上多出一條 PR,標題是一句繁體中文摘要、分支名仍是 ASCII,兩者不一樣。既有 PR 的標題與描述只在內容變了才被 `pr-edit` 改過,依賴被 `pr-depend` 掛上。PR 描述照 `templates/pr-description.md` 生成,各節不留空。功能主幹不在 origin 上時,`tools/base-branch.sh --derive` 會自動從 develop 建出 `{類型}/{功能}/main` 並推上 origin。本地 git 歷史含 `jsc-git:commit` 建立的 commit,目前分支切到目標分支。PR 意見的回覆留在 Gitea 那幾則意見底下。描述檔草稿寫在暫存檔。這一支不寫 wiki 頁,只讀 PLAN 頁與 ANALYZE 頁。 | +| 完成條件 | 握有一個 PR 網址,送出的標題是一句繁體中文摘要,不是分支名。前置依賴已經處理完:`pr-depend` 印出 `OK` 行,或 PR 標題冠上 `WIP:` 且描述指名前置 PR,或描述寫「無」前置 PR 並在回報裡說明。既有 PR 的三個校準項目各自回報為相符或已更新,標題那一項說明拿什麼內容去判定,送出的 API 呼叫數等於不同的項目數。每一則已處理的意見握有回覆連結或記下失敗理由。回報含 `{owner}/{repo}`、PR 編號、PR 網址、PR 摘要四欄,並指名基底分支、目標分支、更新過的校準項目、自動建立的功能主幹、沒回覆到的意見。收尾一定要寫一筆 `skill-end` 狀態事件:拿到 PR 網址且前置依賴處理完是 `ok`,`base-branch.sh` 回 3、4、7、8、9 讓分支還沒推上去就停住是 `blocked`,`pr-depend` 回 4 退成 `WIP:` 或有意見沒回覆到是 `degraded`,`pick-type.sh` 回 3、推送失敗、`pr-create` 回 4、`pr-edit` 回非零是 `failed`,`pick-type.sh` 回 2 代表沒有可提交的變更、沒有 PR 好開,是 `aborted` 不是 `failed`,使用者在確認關卡喊停也是 `aborted`。 | +| 可驗證跡象 | origin 上多出目標分支的 ref,`git ls-remote --heads origin` 查得到。Gitea 上多出一條 PR,標題是一句繁體中文摘要、分支名仍是 ASCII,兩者不一樣。既有 PR 的標題與描述只在內容變了才被 `pr-edit` 改過,依賴被 `pr-depend` 掛上。PR 描述照 `templates/pr-description.md` 生成,各節不留空。功能主幹不在 origin 上時,`tools/base-branch.sh --derive` 會自動從 develop 建出 `{類型}/{功能}/main` 並推上 origin。本地 git 歷史含 `jsc-git:commit` 建立的 commit,目前分支切到目標分支。PR 意見的回覆留在 Gitea 那幾則意見底下。描述檔草稿寫在暫存檔。這一支不寫 wiki 頁,只讀 PLAN 頁與 ANALYZE 頁。跑完 `$JSC_HOME/usage/events.jsonl` 會多一筆 `{kind:skill,phase:end}` 事件,`name` 欄是 `jsc-git:pr`,`status` 與 `exit` 兩欄對得上上一列講的判準;沒有可提交的變更那一輪,事件上是 `aborted` 加結束碼 2,和開出 PR 的那一輪一眼分得開;`jsc-hooks` 不在這台機器上時沒有這一筆,技能本身照樣跑完。 | diff --git a/skills/commit/SKILL.md b/skills/commit/SKILL.md index cd999ba..64cdf78 100644 --- a/skills/commit/SKILL.md +++ b/skills/commit/SKILL.md @@ -24,6 +24,23 @@ description: 'Group all pending file changes by conventional type and feature, t - Exit 4 (API call failed) → report the failure and leave the PR state as unknown. Never read a failed call as "no open PR": that leaves a stale title on a branch that just gained commits. - Any other non-zero → treat it as exit 4. - Done when the report states exactly one of these: the lookup was skipped because `jsc-git:pr` called this run, the branch has no open PR, `jsc-git:pr` reported each of the three items as matched or updated, or the lookup failed and the failure is named. +6. Record how this run ended, as the very last thing this skill does: + + `jsc-hooks/tools/report-status.sh skill-end jsc-git:commit {status} {exit} "{detail}"` + + Resolve that path the way step 2 already resolves `jsc-hooks/hooks/comment-scope.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 same way step 2 commits anyway when the sweep is not installed. The script swallows its own write errors and exits 0 even then, so nothing branches on its code either. Commits that landed stay landed whether or not the run could be recorded. + + | status | This skill's case | + | --- | --- | + | `ok` | Every group is committed, `git status --porcelain` prints nothing, and step 5 stated one of its four outcomes. A step 2 sweep that was skipped because the script is not on this machine is still `ok` — say so in `{detail}`, since a run judged without the sweep is worth telling apart from one the sweep passed | + | `blocked` | The `jsc-hooks` PreToolUse `Bash` guard rejected the commit before git ran — a bulk `git add -A` pair, or a message carrying no Traditional Chinese — so no commit landed and the working tree is exactly as it was | + | `degraded` | Every group is committed and the tree is clean, but the close-out is short: step 5's `pr-of-branch` returned 4, or the `jsc-git:pr` calibration failed, so the open PR still carries a title written before these commits | + | `failed` | A `git add` or `git commit` returned non-zero part-way through, leaving some groups committed and the tree dirty. Report the group that broke; a partial commit set is what the next run has to reconcile | + | `aborted` | Step 1's inventory came back empty, so there was nothing to commit and nothing was attempted. Also the user stopping the run at the grouping or the message-style question. This is not a success: a run that committed nothing must not read like a run that committed everything | + + `{exit}` is the exit code of whatever decided the status, `0` for `ok`. `{detail}` is one short line well under 200 characters: group and commit counts plus exit codes, never commit messages, branch names, or personal data. + + Done when the command has run, or the script was absent and this step was skipped. ## Type table diff --git a/skills/pr/SKILL.md b/skills/pr/SKILL.md index 4ac82ae..4b9a2ba 100644 --- a/skills/pr/SKILL.md +++ b/skills/pr/SKILL.md @@ -77,6 +77,23 @@ The ladder rules live in one place only: section 「PR 分支階梯」 of `jsc-m 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. +10. 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 | + | --- | --- | + | `ok` | A PR URL is held, the prerequisite is settled by a `pr-depend` exit 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 is `ok` as well: silence is the correct outcome there | + | `blocked` | The ladder refused before anything was pushed: `base-branch.sh` exit 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 opened | + | `degraded` | The PR is open but part of the close-out did not land: `pr-depend` exit 4 sent the run to the `WIP:` fallback, so the dependency is not on the PR, or a handled comment ended with a recorded failure instead of a reply link | + | `failed` | The run got partway and then git or the API refused: `pick-type.sh` exit 3 (commits exist but no subject carries a ladder type), a push that failed, `pr-create` exit 4, or a non-zero `pr-edit` that left the old title and description in place | + | `aborted` | **`pick-type.sh` exit 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 before `pr-create`, `pr-edit` or `pr-depend` is `aborted` too | + + `{exit}` is the exit code of whatever decided the status, `0` for `ok` — so the no-change round above carries `aborted` with `2`. `{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 From 2e42badea0af6dfd7eb8854d8c7f175dde33daee Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 2 Sep 2026 16:01:14 +0800 Subject: [PATCH 2/5] =?UTF-8?q?chore(plugin=20=E7=89=88=E6=9C=AC):=20?= =?UTF-8?q?=E4=B8=89=E4=BB=BD=20manifest=20=E5=8D=87=E7=89=88=E8=87=B3=200?= =?UTF-8?q?.1.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index e3ab7f1..2844f7d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.4", + "version": "0.1.5", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index ce74d7b..016c096 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.4", + "version": "0.1.5", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "jsc": { diff --git a/plugin.json b/plugin.json index 22720a9..06a6c22 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.4", + "version": "0.1.5", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills/", "jsc": { From 5d3f76167754a1463e9e6544962c478995f6c3c7 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 4 Sep 2026 10:43:54 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(pr):=20=E8=87=AA=E5=AE=B6=E4=B8=89?= =?UTF-8?q?=E6=94=AF=E8=85=B3=E6=9C=AC=E7=9A=84=E8=B7=AF=E5=BE=91=E8=A3=9C?= =?UTF-8?q?=E4=B8=8A=E5=A4=96=E6=8E=9B=E7=9B=AE=E9=8C=84=E5=90=8D=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=9B=B8=E5=B0=8D=E6=96=BC=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E7=9B=AE=E9=8C=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- skills/pr/SKILL.md | 18 ++++++++++-------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2844f7d..cd1d28e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.5", + "version": "0.1.7", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 016c096..5e56659 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.5", + "version": "0.1.7", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "jsc": { diff --git a/plugin.json b/plugin.json index 06a6c22..1a67860 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.5", + "version": "0.1.7", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills/", "jsc": { diff --git a/skills/pr/SKILL.md b/skills/pr/SKILL.md index 4b9a2ba..ed01738 100644 --- a/skills/pr/SKILL.md +++ b/skills/pr/SKILL.md @@ -1,15 +1,17 @@ --- 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. +description: 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`. `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. +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 -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: +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 `jsc-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. @@ -20,12 +22,12 @@ The ladder rules live in one place only: section 「PR 分支階梯」 of `jsc-m - 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. + 1. 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. 2. 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. - 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. Translate the feature and the title into short English phrases, then build the name with `jsc-git/tools/slugify.sh`. `fix` takes 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`, then `jsc-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. 4. Resolve the base branch: - - Run `tools/base-branch.sh --derive {target branch}`. Exit 0 → the script printed one branch name. + - 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 `--derive` flag was dropped. Rerun with the flag. @@ -42,7 +44,7 @@ The ladder rules live in one place only: section 「PR 分支階梯」 of `jsc-m - 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}`. 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 `tools/base-branch.sh` parse, 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 in `jsc-meta/references/ste100.md` names 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. + - 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.sh` parse, 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 in `jsc-meta/references/ste100.md` names 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. @@ -100,4 +102,4 @@ The ladder rules live in one place only: section 「PR 分支階梯」 of `jsc-m 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 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. PR titles and descriptions take the opposite rule: they stay Traditional Chinese per `jsc-meta/references/ste100.md`. The two never copy each other. +4. Branch names are ASCII only (`a-z0-9`, `/`, `-`). Traditional Chinese phrases go through `jsc-git/tools/slugify.sh` first; `jsc-git/tools/base-branch.sh --derive` rejects anything else. PR titles and descriptions take the opposite rule: they stay Traditional Chinese per `jsc-meta/references/ste100.md`. The two never copy each other. From 003593c73a42c7d7b3b99f1ba3e2129cceee3690 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 4 Sep 2026 10:51:31 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix(pr):=20=E8=A1=8C=E7=82=BA=E6=B8=85?= =?UTF-8?q?=E5=96=AE=E9=82=A3=E4=B8=89=E6=94=AF=E8=85=B3=E6=9C=AC=E7=9A=84?= =?UTF-8?q?=E8=B7=AF=E5=BE=91=E4=B9=9F=E8=A3=9C=E4=B8=8A=E5=A4=96=E6=8E=9B?= =?UTF-8?q?=E7=9B=AE=E9=8C=84=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一筆只改了技能本文,行為清單漏掉。新的路徑檢核腳本一跑就指出來——關鍵 步驟與外部呼叫兩欄還留著八處不帶前綴的寫法,而那兩欄正是別人拿來確認這支 技能會呼叫誰的地方。 順手改掉技能本文開頭那一句的舉例方式。原本把不帶前綴的錯誤寫法原樣寫出來 當例子,那一行自己就會被路徑檢核當成一處待改。改成描述後果、不照抄那個 寫法,順便講得更準:省掉外掛目錄名之後,路徑會相對於當下的工作目錄解, 可能解到自己這個存取庫裡對的那一支、可能解到別的 domain 底下另一支,也可能 什麼都解不到。中間那一種最糟,它會跑起來,跑的是另一支腳本。 Co-Authored-By: Claude Opus 5 --- references/behaviors.md | 6 +++--- skills/pr/SKILL.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/references/behaviors.md b/references/behaviors.md index b4a5645..437b44a 100644 --- a/references/behaviors.md +++ b/references/behaviors.md @@ -17,7 +17,7 @@ | 項目 | 內容 | | --- | --- | | 觸發時機 | 工作做完要送審時叫用。既有 PR 在新 commit 之後要重新同步時也叫用。只想提交不想推送時不叫用這一支,改叫 `jsc-git:commit`。 | -| 關鍵步驟 | 先用 `tools/base-branch.sh {呼叫方基底}` 驗證呼叫方傳進來的基底、叫 `jsc-git:commit` 提交全部變更、用 `tools/pick-type.sh` 從 commit 標題選出型別、用 `tools/slugify.sh` 組出階梯狀目標分支名、用 `tools/base-branch.sh --derive {目標分支}` 推導基底並和呼叫方基底比對、以 `git checkout -b {目標分支} origin/{基底分支}` 建分支並 `git push -u origin` 推上去、用 `gitea.sh pr-of-branch` 查一次開啟中的 PR、沒有就用 `gitea.sh pr-create` 開新 PR 並用 `gitea.sh pr-depend` 掛前置依賴、已經有就只更新標題、描述、前置依賴三項裡不同的那幾項、回覆已處理的 PR 意見、回報,最後跑 `jsc-hooks/tools/report-status.sh skill-end jsc-git:pr {status} {結束碼} {detail}` 記下本輪結果,腳本不在這台機器上就安靜跳過。PR 標題寫一句繁體中文摘要,說明這條 PR 做了什麼,不套用分支名;校準既有 PR 時只問標題還描述不描述得了目前的內容,不拿分支名比對。 | -| 外部呼叫 | `tools/base-branch.sh`、`tools/pick-type.sh`、`tools/slugify.sh`、`templates/pr-description.md`、`jsc-git:commit`、`jsc-gitea/tools/gitea.sh` 的 `pr-of-branch`、`pr-create`、`pr-edit`、`pr-depend`、`comment-reply`、`jsc-gitea:wiki`(讀 PLAN 頁與 ANALYZE 頁)、`jsc-ask:ask`(基底衝突與分支不存在時發問)、`jsc-meta/references/guidelines.md` 的「PR 分支階梯」、`jsc-meta/references/pr-report.md`、`git checkout`、`git cherry-pick`、`git push`、`git ls-remote`。分支標題摘要與描述草稿交給 sub agent 處理。 | +| 關鍵步驟 | 先用 `jsc-git/tools/base-branch.sh {呼叫方基底}` 驗證呼叫方傳進來的基底、叫 `jsc-git:commit` 提交全部變更、用 `jsc-git/tools/pick-type.sh` 從 commit 標題選出型別、用 `jsc-git/tools/slugify.sh` 組出階梯狀目標分支名、用 `jsc-git/tools/base-branch.sh --derive {目標分支}` 推導基底並和呼叫方基底比對、以 `git checkout -b {目標分支} origin/{基底分支}` 建分支並 `git push -u origin` 推上去、用 `gitea.sh pr-of-branch` 查一次開啟中的 PR、沒有就用 `gitea.sh pr-create` 開新 PR 並用 `gitea.sh pr-depend` 掛前置依賴、已經有就只更新標題、描述、前置依賴三項裡不同的那幾項、回覆已處理的 PR 意見、回報,最後跑 `jsc-hooks/tools/report-status.sh skill-end jsc-git:pr {status} {結束碼} {detail}` 記下本輪結果,腳本不在這台機器上就安靜跳過。PR 標題寫一句繁體中文摘要,說明這條 PR 做了什麼,不套用分支名;校準既有 PR 時只問標題還描述不描述得了目前的內容,不拿分支名比對。 | +| 外部呼叫 | `jsc-git/tools/base-branch.sh`、`jsc-git/tools/pick-type.sh`、`jsc-git/tools/slugify.sh`、`templates/pr-description.md`、`jsc-git:commit`、`jsc-gitea/tools/gitea.sh` 的 `pr-of-branch`、`pr-create`、`pr-edit`、`pr-depend`、`comment-reply`、`jsc-gitea:wiki`(讀 PLAN 頁與 ANALYZE 頁)、`jsc-ask:ask`(基底衝突與分支不存在時發問)、`jsc-meta/references/guidelines.md` 的「PR 分支階梯」、`jsc-meta/references/pr-report.md`、`git checkout`、`git cherry-pick`、`git push`、`git ls-remote`。分支標題摘要與描述草稿交給 sub agent 處理。 | | 完成條件 | 握有一個 PR 網址,送出的標題是一句繁體中文摘要,不是分支名。前置依賴已經處理完:`pr-depend` 印出 `OK` 行,或 PR 標題冠上 `WIP:` 且描述指名前置 PR,或描述寫「無」前置 PR 並在回報裡說明。既有 PR 的三個校準項目各自回報為相符或已更新,標題那一項說明拿什麼內容去判定,送出的 API 呼叫數等於不同的項目數。每一則已處理的意見握有回覆連結或記下失敗理由。回報含 `{owner}/{repo}`、PR 編號、PR 網址、PR 摘要四欄,並指名基底分支、目標分支、更新過的校準項目、自動建立的功能主幹、沒回覆到的意見。收尾一定要寫一筆 `skill-end` 狀態事件:拿到 PR 網址且前置依賴處理完是 `ok`,`base-branch.sh` 回 3、4、7、8、9 讓分支還沒推上去就停住是 `blocked`,`pr-depend` 回 4 退成 `WIP:` 或有意見沒回覆到是 `degraded`,`pick-type.sh` 回 3、推送失敗、`pr-create` 回 4、`pr-edit` 回非零是 `failed`,`pick-type.sh` 回 2 代表沒有可提交的變更、沒有 PR 好開,是 `aborted` 不是 `failed`,使用者在確認關卡喊停也是 `aborted`。 | -| 可驗證跡象 | origin 上多出目標分支的 ref,`git ls-remote --heads origin` 查得到。Gitea 上多出一條 PR,標題是一句繁體中文摘要、分支名仍是 ASCII,兩者不一樣。既有 PR 的標題與描述只在內容變了才被 `pr-edit` 改過,依賴被 `pr-depend` 掛上。PR 描述照 `templates/pr-description.md` 生成,各節不留空。功能主幹不在 origin 上時,`tools/base-branch.sh --derive` 會自動從 develop 建出 `{類型}/{功能}/main` 並推上 origin。本地 git 歷史含 `jsc-git:commit` 建立的 commit,目前分支切到目標分支。PR 意見的回覆留在 Gitea 那幾則意見底下。描述檔草稿寫在暫存檔。這一支不寫 wiki 頁,只讀 PLAN 頁與 ANALYZE 頁。跑完 `$JSC_HOME/usage/events.jsonl` 會多一筆 `{kind:skill,phase:end}` 事件,`name` 欄是 `jsc-git:pr`,`status` 與 `exit` 兩欄對得上上一列講的判準;沒有可提交的變更那一輪,事件上是 `aborted` 加結束碼 2,和開出 PR 的那一輪一眼分得開;`jsc-hooks` 不在這台機器上時沒有這一筆,技能本身照樣跑完。 | +| 可驗證跡象 | origin 上多出目標分支的 ref,`git ls-remote --heads origin` 查得到。Gitea 上多出一條 PR,標題是一句繁體中文摘要、分支名仍是 ASCII,兩者不一樣。既有 PR 的標題與描述只在內容變了才被 `pr-edit` 改過,依賴被 `pr-depend` 掛上。PR 描述照 `templates/pr-description.md` 生成,各節不留空。功能主幹不在 origin 上時,`jsc-git/tools/base-branch.sh --derive` 會自動從 develop 建出 `{類型}/{功能}/main` 並推上 origin。本地 git 歷史含 `jsc-git:commit` 建立的 commit,目前分支切到目標分支。PR 意見的回覆留在 Gitea 那幾則意見底下。描述檔草稿寫在暫存檔。這一支不寫 wiki 頁,只讀 PLAN 頁與 ANALYZE 頁。跑完 `$JSC_HOME/usage/events.jsonl` 會多一筆 `{kind:skill,phase:end}` 事件,`name` 欄是 `jsc-git:pr`,`status` 與 `exit` 兩欄對得上上一列講的判準;沒有可提交的變更那一輪,事件上是 `aborted` 加結束碼 2,和開出 PR 的那一輪一眼分得開;`jsc-hooks` 不在這台機器上時沒有這一筆,技能本身照樣跑完。 | diff --git a/skills/pr/SKILL.md b/skills/pr/SKILL.md index ed01738..e1871f8 100644 --- a/skills/pr/SKILL.md +++ b/skills/pr/SKILL.md @@ -7,7 +7,7 @@ description: Commit all changes via jsc-git:commit, name a ladder-shaped target 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. +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 dropping the plugin directory name leaves a path that resolves against whatever the current directory happens to be — the right script inside this repo, a different domain's script outside it, or nothing at all and exit 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 From 6965e3b24cb61f7d16ae16e7d7fefc3e9af7c15d Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 4 Sep 2026 11:20:16 +0800 Subject: [PATCH 5/5] =?UTF-8?q?chore(plugin):=20=E4=B8=89=E4=BB=BD=20manif?= =?UTF-8?q?est=20=E5=8D=87=E7=89=88=E8=87=B3=200.1.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一輪的行為清單修正沒趕上合併,這一筆帶著它重新送出。 Co-Authored-By: Claude Opus 5 --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index cd1d28e..57b59ab 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.7", + "version": "0.1.8", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 5e56659..2efe1c0 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.7", + "version": "0.1.8", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills", "jsc": { diff --git a/plugin.json b/plugin.json index 1a67860..78b1ac5 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc-git", - "version": "0.1.7", + "version": "0.1.8", "description": "Commit 分組認可與 Push Request 建立", "skills": "./skills/", "jsc": {