Merge pull request 'fix/skillset-audit-compliance-and-guard-fixes' (#8) from fix/skillset-audit-compliance-and-guard-fixes into develop

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2026-08-25 07:14:49 +00:00
10 changed files with 96 additions and 22 deletions
+3 -3
View File
@@ -43,7 +43,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git" "url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
}, },
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
}, },
{ {
"name": "jsc-log", "name": "jsc-log",
@@ -75,7 +75,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git" "url": "https://gitea.jsc.idv.tw/plugins/review.git"
}, },
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
}, },
{ {
"name": "jsc-sdlc", "name": "jsc-sdlc",
@@ -83,7 +83,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
}, },
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
} }
] ]
} }
+3 -3
View File
@@ -43,7 +43,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git" "url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
}, },
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄" "description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
}, },
{ {
"name": "jsc-log", "name": "jsc-log",
@@ -75,7 +75,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git" "url": "https://gitea.jsc.idv.tw/plugins/review.git"
}, },
"description": "程式碼審查:Refactoring 壞味道六組 + 註解規範 + 淺模組" "description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
}, },
{ {
"name": "jsc-sdlc", "name": "jsc-sdlc",
@@ -83,7 +83,7 @@
"source": "url", "source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git" "url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
}, },
"description": "開發生命週期:規劃/分析/實作/維護(wiki 追蹤)" "description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
} }
] ]
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-git", "name": "jsc-git",
"version": "0.0.4", "version": "0.0.6",
"description": "Commit 分組認可與 Push Request 建立", "description": "Commit 分組認可與 Push Request 建立",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-git", "name": "jsc-git",
"version": "0.0.4", "version": "0.0.6",
"description": "Commit 分組認可與 Push Request 建立", "description": "Commit 分組認可與 Push Request 建立",
"skills": "./skills" "skills": "./skills"
} }
+8 -1
View File
@@ -18,6 +18,13 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
> 舊入口 `plugins/jsc` 已移除,marketplace 正本移到 `plugins/meta`。marketplace 名稱仍是 `jsc`(取自 marketplace.json 的 `name` 欄位,與存取庫名無關),安裝 token 不變;已從舊入口安裝過的人先執行 `claude plugin marketplace remove jsc`,再依上表重新 add。 > 舊入口 `plugins/jsc` 已移除,marketplace 正本移到 `plugins/meta`。marketplace 名稱仍是 `jsc`(取自 marketplace.json 的 `name` 欄位,與存取庫名無關),安裝 token 不變;已從舊入口安裝過的人先執行 `claude plugin marketplace remove jsc`,再依上表重新 add。
## 工具
| 檔案 | 用途 |
| --- | --- |
| `tools/base-branch.sh` | 決定 PR 基底分支:呼叫方傳入的分支最優先,沒傳才依序試 develop、main、master;一律確認分支存在於遠端,找不到就回傳非零。「沒傳」看參數個數,傳入空字串算錯誤,不會退回 develop |
| `tools/slugify.sh` | 把類型與英文短語組成 ASCII 分支名 `{type}/{slug}`;輸入含非 ASCII 或 slug 化後為空,就回傳非零並要求先翻譯成英文短語 |
## Skills 目錄 ## Skills 目錄
呼叫方式:Claude / Antigravity `/jsc-git:{name}`;Codex `${name}`;Copilot / Kiro 描述需求自動觸發。 呼叫方式:Claude / Antigravity `/jsc-git:{name}`;Codex `${name}`;Copilot / Kiro 描述需求自動觸發。
@@ -30,7 +37,7 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `pr` ### `pr`
先認可所有變更,再從 develop / main 建立目標分支(分支名只允許 ASCII:類型取 commit 優先度最高者,標題翻譯成英文短語後 slug 化)、push、以範本描述建立 Gitea PR。 先認可所有變更,再建立目標分支、push、以範本描述建立 Gitea PR。基底分支優先採用呼叫方傳入的分支;呼叫方沒傳,才依序退回 develop、main、master。分支名只允許 ASCII:類型取 commit 優先度最高者,標題先翻譯成英文短語再 slug 化。
<!-- JSC-SKILLS:END --> <!-- JSC-SKILLS:END -->
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-git", "name": "jsc-git",
"version": "0.0.4", "version": "0.0.6",
"description": "Commit 分組認可與 Push Request 建立", "description": "Commit 分組認可與 Push Request 建立",
"skills": "./skills/" "skills": "./skills/"
} }
+3 -3
View File
@@ -7,9 +7,9 @@ description: Group all pending file changes by conventional type and feature, th
## Steps ## Steps
1. Track every file change: inspect all changes with `git status --porcelain` first, then `git add` group by group. Never `git add -A` and commit everything at once. 1. Track every file change: inspect all changes with `git status --porcelain` first, then `git add` group by group. `git add -A` followed by one bulk commit is forbidden. Done when every path listed by `git status --porcelain` is assigned to exactly one group, before the first commit runs.
2. Group the changes by same type plus same requirement or feature. One group is one commit. 2. Group the changes by same type plus same requirement or feature. One group is one commit. Done when each group carries one type and one requirement or feature, and no path sits in two groups.
3. Commit each group with the format `{type}({requirement or feature}): {message}`. 3. Commit each group with the format `{type}({requirement or feature}): {message}`. Done when `git status --porcelain` returns empty; report done only then.
## Type table ## Type table
+11 -9
View File
@@ -7,23 +7,25 @@ description: Commit all changes via jsc-git:commit, create a target branch named
## Steps ## Steps
1. Call `jsc-git:commit` to commit every file change first. 1. Call `jsc-git:commit` to commit every file change first. Done when `git status --porcelain` prints nothing.
2. Pick the base branch. **A base branch passed in by the caller wins over everything else** — `jsc-sdlc:implement` passes the analysis page's source branch, and silently retargeting that PR at `develop` would merge a single work package straight past the feature branch it belongs to. Only when no caller supplied one: use `develop` when it exists on the remote, else `main`, else `master`. Verify the chosen base exists on the remote (`git fetch --prune origin` first, then `git branch -r`); it does not exist → report and stop, never fall back silently. 2. Resolve the base branch: `tools/base-branch.sh {base branch the caller passed in, omitted when the caller passed none}`. **A base branch passed in by the caller wins over everything else** — `jsc-sdlc:implement` passes the analysis page's source branch, and silently retargeting that PR at `develop` would merge a single work package straight past the feature branch it belongs to. Done when the script prints one branch name; a non-zero exit → report and stop, never fall back silently.
3. Create the target branch from the remote base branch: 3. Create the target branch from the remote base branch:
1. Take the type with the highest priority across all commits: `revert > fix > feat > perf > refactor > test > docs > style > chore`. 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. 2. Summarize one title from all commit messages. Done when one title line covers every commit in the range.
3. Branch names allow **ASCII only**; always slugify as `{type}/{requirement or feature}-{title}`. Translate the requirement and title into a short English phrase first, then call `tools/slugify.sh {type} {phrase}` to produce the final slug (example: translate a non-English request to English first, then slugify — "export report" → export-report → feat/order-export-report). No spaces, parentheses, colons, or any non-ASCII character. 3. Translate the requirement and the title into a short English phrase, then run `tools/slugify.sh {type} {phrase}` to build the target branch name `{type}/{requirement or feature}-{title}` (example: a non-English request to export order reports becomes the phrase `order export report`, then `feat/order-export-report`). Done when the script prints one slug on stdout; exit 2 (non-ASCII input) or exit 3 (empty slug) → re-translate the title into an English phrase and retry, never hand-build the branch name.
4. Run `git push -u origin {target branch}`. 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.
4. Run `git push -u origin {target branch}`. Done when `git ls-remote --heads origin` shows the target branch's ref.
5. Create the PR: `jsc-gitea/tools/gitea.sh pr-create {owner}/{repo} {target branch} {base branch} "{branch name}" {description file}`. 5. Create the PR: `jsc-gitea/tools/gitea.sh pr-create {owner}/{repo} {target branch} {base branch} "{branch name}" {description file}`.
- Title = branch name. - 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). - 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.
6. **Prerequisite PR blocking**: when the description lists a prerequisite Push Request, run 6. **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}` `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. If the API is unavailable, degrade: prefix the PR title with `WIP:` (Gitea blocks merging natively) and remove it once the prerequisite is done. 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.
7. Report the PR URL. 7. Report the PR URL. Done when the reported URL is the one step 5 printed, together with the base branch and the target branch it was opened against.
## Rules ## Rules
1. No template section may stay empty: fill the literal 「無」 when there is no plan page, analyze page, or prerequisite PR. 1. No template section may stay empty: fill the literal 「無」 when there is no plan page, analyze page, or prerequisite PR.
2. Take the plan page and analyze page links from the `jsc-sdlc` wiki pages; the analyze link must point at the work package heading anchor. 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. 3. Branch title summarization (step 3.2) and description drafting MUST run as a sub agent.
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env sh
# base-branch.sh — 決定 PR 的基底分支。
# 用法: base-branch.sh [caller-base]
# 規則: 呼叫方傳入的分支最優先;呼叫方沒傳,才依序試 develop、main、master。
# 先 git fetch --prune origin,再確認選中的分支存在於遠端。
# 「有沒有傳」看參數個數,不看參數內容:呼叫方寫 base-branch.sh "$SOURCE_BRANCH"
# 而變數沒設定時,那是空字串,不是沒傳。若當成沒傳就會悄悄退回 develop,
# 把單一工作包直接合進 develop——這支腳本存在的目的就是擋這件事。
# 輸出: 選中的分支名(一行)。
# 護欄: 參數過多回傳 2;抓不到遠端回傳 3;呼叫方指定的分支不在遠端回傳 4;
# develop、main、master 都不在遠端回傳 5;呼叫方傳入空字串回傳 6。
# 錯誤訊息一律印繁中到 stderr。
set -u
if [ "$#" -gt 1 ]; then
echo "用法: base-branch.sh [caller-base]" >&2
exit 2
fi
ARGC=$#
CALLER=${1:-}
if [ "$ARGC" -eq 1 ] && [ -z "$CALLER" ]; then
echo "錯誤: 呼叫方傳入空字串當基底分支。請確認來源分支變數有值,或整個參數不要傳。" >&2
exit 6
fi
if ! git fetch --prune origin >/dev/null 2>&1; then
echo "錯誤: 無法向 origin 取得遠端分支。請確認遠端可以連線,再重試。" >&2
exit 3
fi
remote_has() {
[ -n "$(git ls-remote --heads origin "refs/heads/$1" 2>/dev/null)" ]
}
if [ "$ARGC" -eq 1 ]; then
if remote_has "$CALLER"; then
printf '%s\n' "$CALLER"
exit 0
fi
echo "錯誤: 呼叫方指定的基底分支 $CALLER 不在 origin 上。請確認分支名,不要自行改用其他分支。" >&2
exit 4
fi
for candidate in develop main master; do
if remote_has "$candidate"; then
printf '%s\n' "$candidate"
exit 0
fi
done
echo "錯誤: origin 上找不到 develop、main、master。請由呼叫方指定基底分支。" >&2
exit 5
+11
View File
@@ -4,6 +4,7 @@
# 規則: 全部轉小寫,非 a-z0-9 的字元換成連字號, # 規則: 全部轉小寫,非 a-z0-9 的字元換成連字號,
# 連續連字號合併成一個,並去除開頭與結尾的連字號。 # 連續連字號合併成一個,並去除開頭與結尾的連字號。
# 輸出: {type}/{slug}(例如: slugify.sh feat "export report" → feat/export-report) # 輸出: {type}/{slug}(例如: slugify.sh feat "export report" → feat/export-report)
# 護欄: 輸入含非 ASCII 字元回傳 2;slug 化後為空回傳 3。兩者都印繁中錯誤到 stderr。
set -u set -u
if [ "$#" -lt 2 ]; then if [ "$#" -lt 2 ]; then
@@ -17,4 +18,14 @@ PHRASE="$*"
slug=$(printf '%s' "$PHRASE" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9]/-/g' -e 's/-\{2,\}/-/g' -e 's/^-//' -e 's/-$//') slug=$(printf '%s' "$PHRASE" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9]/-/g' -e 's/-\{2,\}/-/g' -e 's/^-//' -e 's/-$//')
if printf '%s%s' "$TYPE" "$PHRASE" | LC_ALL=C grep -q '[^ -~]'; then
echo "錯誤: 分支名只允許 ASCII。請先把標題翻譯成英文短語,再交給 slugify.sh。" >&2
exit 2
fi
if [ -z "$slug" ]; then
echo "錯誤: 標題 slug 化後是空字串,組不出分支名。請先把標題翻譯成英文短語,再交給 slugify.sh。" >&2
exit 3
fi
printf '%s/%s\n' "$TYPE" "$slug" printf '%s/%s\n' "$TYPE" "$slug"