Merge pull request 'refactor(code): 接上 shared 共用規範,去除重抄段落並補模型檢查' (#19) from develop into master

Reviewed-on: #19
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
This commit was merged in pull request #19.
This commit is contained in:
2026-08-11 06:19:11 +00:00
14 changed files with 215 additions and 671 deletions
+11 -3
View File
@@ -1,12 +1,20 @@
{ {
"name": "jsc-code", "name": "jsc-code",
"version": "0.0.3", "version": "0.1.0",
"description": "JSC 程式碼工作流 plugin(Claude Code / Codex / Antigravity / OpenCode),提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程;於 Claude Code 以 /jsc-code: 前綴呼叫。", "description": "JSC 程式碼工作流 plugin(Claude Code / Codex / Antigravity / OpenCode / GitHub Copilot CLI),提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程;於 Claude Code 以 /jsc-code: 前綴呼叫。",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
"name": "JSC" "name": "JSC"
}, },
"homepage": "https://gitea.jsc.idv.tw/plugins/code", "homepage": "https://gitea.jsc.idv.tw/plugins/code",
"repository": "https://gitea.jsc.idv.tw/plugins/code.git", "repository": "https://gitea.jsc.idv.tw/plugins/code.git",
"keywords": ["code-review", "git-diff", "gitea", "nuget", "docker", "skills", "jsc"] "keywords": [
"code-review",
"git-diff",
"gitea",
"nuget",
"docker",
"skills",
"jsc"
]
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-code", "name": "jsc-code",
"version": "0.0.3", "version": "0.1.0",
"description": "JSC 程式碼工作流 plugin,提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程。", "description": "JSC 程式碼工作流 plugin,提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程。",
"skills": "./skills" "skills": "./skills"
} }
+10 -110
View File
@@ -54,117 +54,16 @@ code/
## 安裝 / 更新 / 移除(各助理) ## 安裝 / 更新 / 移除(各助理)
> 指令中的 repo 網址換成你的:`https://gitea.jsc.idv.tw/plugins/code.git` > 完整的安裝/更新/移除指令(Claude Code、Codex、Antigravity、OpenCode、GitHub Copilot CLI 五種助理),一律以 [`/jsc-shared:spec-plugin-cli`](https://gitea.jsc.idv.tw/plugins/shared/src/branch/master/skills/spec-plugin-cli/SKILL.md) 為唯一權威版本,套用時代入下列佔位符:
> >
> **Claude / Codex 從 git URL 安裝(會 clone 遠端),請先把本 repo `push` 到 gitea。** > | 佔位符 | 值 |
> **Antigravity 的 `agy plugin install <url>` 目前只支援 github.com**;gitea 請改用「clone + 本地路徑」(見 Antigravity 節)。 > | --- | --- |
> 本機/離線:Claude 可用本地路徑加 marketplace;Antigravity 用本地路徑安裝。 > | `<host>` | `gitea.jsc.idv.tw` |
> | `<name>` | `code` |
### Claude Code > | `<plugin>` | `jsc-code` |
> | `<marketplace>` | `code` |
```bash > | `<token>`(= `<plugin>@<marketplace>`) | `jsc-code@code` |
# 安裝 > | `<url>` | `https://gitea.jsc.idv.tw/plugins/code.git` |
claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/code.git
claude plugin install jsc-code@code
# 更新
claude plugin marketplace update code
claude plugin update jsc-code@code
# 移除
claude plugin uninstall jsc-code@code
claude plugin marketplace remove code
```
- 工作階段內 slash 版(等價):把 `claude plugin` 換成 `/plugin`。
- 本機開發(免 push):`claude plugin marketplace add C:\Users\h3285\source\repos.plugins\code`(本地路徑)後再 install。
- **呼叫**:`/jsc-code:<name>`(例 `/jsc-code:issues`)。
### Codex
```bash
# 安裝
codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/code.git
codex plugin add jsc-code@code
# 更新(重新抓取 marketplace 的 git 快照)
codex plugin marketplace upgrade code
# 移除
codex plugin remove jsc-code@code
codex plugin marketplace remove code
```
- 安裝 token `jsc-code@code` = plugin 名(`.codex-plugin/plugin.json` 的 `name`)@ marketplace 名(`.agents/plugins/marketplace.json` 的 `name`)。
- 本 repo 的 Codex marketplace 以 `url` 來源指向自己,故 Codex **一律從 gitea 安裝**(需先 push);安裝後重啟 Codex。
- **呼叫**:`$<name>`(例 `$issues`),或用 `/skills` 選單。
### Antigravity(`agy`)
> `agy plugin install <url>` 目前**只支援 github.com**;gitea 等自架 git 不支援 URL 安裝,請先 `git clone` 再用**本地路徑**安裝。
```bash
# 安裝:clone 後用本地路徑
git clone https://gitea.jsc.idv.tw/plugins/code.git ~/plugins/code
agy plugin install ~/plugins/code
# 更新(agy 無 update 子指令 → git pull 後重裝)
git -C ~/plugins/code pull
agy plugin uninstall jsc-code
agy plugin install ~/plugins/code
# 移除
agy plugin uninstall jsc-code
```
- 若把 skills 放到 GitHub,則可直接 `agy plugin install https://github.com/<owner>/<repo>`。
- 其他:`agy plugin list`、`agy plugin enable jsc-code` / `disable jsc-code`、`agy plugin validate <path>`。安裝後重啟工作階段。
- **呼叫**:`/jsc-code:<name>`(例 `/jsc-code:issues`)或依描述自動觸發。
### OpenCode
OpenCode 的「plugin」是 TypeScript/npm 套件,不適用於 skill 包;skills 改用**目錄安裝**。
OpenCode 會讀 `~/.config/opencode/skills/`(也會讀 `~/.claude/skills/`、`~/.agents/skills/`)。
```bash
# 安裝
git clone https://gitea.jsc.idv.tw/plugins/code.git ~/plugins/code
mkdir -p ~/.config/opencode/skills
cp -r ~/plugins/code/skills/* ~/.config/opencode/skills/
# 更新
git -C ~/plugins/code pull
cp -r ~/plugins/code/skills/* ~/.config/opencode/skills/
# 移除
rm -rf ~/.config/opencode/skills/{review-resolve,issues,nuget,sync,action-docker,action-composite,action-node,image,target}
```
> **Windows PowerShell**:`cp -r A B` → `Copy-Item A B -Recurse -Force`、`rm -rf X` → `Remove-Item X -Recurse -Force`、`~` → `$HOME`。
- **呼叫**:直接描述需求,模型會依 skill 描述自動透過 skill 工具呼叫。
### GitHub Copilot CLI
Copilot CLI 支援與 Claude Code 類似的原生 plugin / marketplace 指令,可直接從 marketplace 安裝、更新與移除本 plugin。
```bash
# 安裝
copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/code.git
copilot plugin install jsc-code@code
# 更新
copilot plugin marketplace update code
copilot plugin update jsc-code@code
# 移除
copilot plugin uninstall jsc-code@code
copilot plugin marketplace remove code
```
- 安裝 token `jsc-code@code` = plugin 名(plugin manifest 的 `name`)@ marketplace 名。
- `copilot plugin marketplace add` 支援 GitHub `owner/repo`、git URL 與本地路徑;Gitea repo 可用上方 HTTPS URL。
- **呼叫**:在 Copilot CLI 中用自然語言描述需求,例如 `copilot -i "請使用 issues 處理 plugins/code 的 12、15 號議題"`。
--- ---
@@ -303,3 +202,4 @@ copilot plugin marketplace remove code
- Antigravity:`git -C ~/plugins/code pull && agy plugin uninstall jsc-code && agy plugin install ~/plugins/code` - Antigravity:`git -C ~/plugins/code pull && agy plugin uninstall jsc-code && agy plugin install ~/plugins/code`
- OpenCode:`git pull` 後重新複製 `skills/` - OpenCode:`git pull` 後重新複製 `skills/`
- Copilot:`copilot plugin marketplace update code && copilot plugin update jsc-code@code` - Copilot:`copilot plugin marketplace update code && copilot plugin update jsc-code@code`
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-code", "name": "jsc-code",
"version": "0.0.3", "version": "0.1.0",
"description": "JSC 程式碼工作流 plugin,提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程;於 Antigravity 以 /jsc-code: 前綴呼叫。", "description": "JSC 程式碼工作流 plugin,提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程;於 Antigravity 以 /jsc-code: 前綴呼叫。",
"skills": "./skills/" "skills": "./skills"
} }
+28
View File
@@ -0,0 +1,28 @@
{
"name": "jsc-code",
"shortName": "code",
"version": "0.1.0",
"descriptionCore": "JSC 程式碼工作流 plugin,提供 Gitea AI review findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步,以及 action / Dockerfile 標準化流程。",
"assistants": ["Claude Code", "Codex", "Antigravity", "OpenCode", "GitHub Copilot CLI"],
"cliPrefix": "/jsc-code:",
"callPrefixAssistants": {
"root": "Antigravity",
"claudePlugin": "Claude Code"
},
"codexNote": "",
"skillsPath": "./skills",
"author": { "name": "JSC" },
"homepage": "https://gitea.jsc.idv.tw/plugins/code",
"repository": "https://gitea.jsc.idv.tw/plugins/code.git",
"keywords": ["code-review", "git-diff", "gitea", "nuget", "docker", "skills", "jsc"],
"marketplace": {
"repoDescription": "JSC 程式碼工作流 skills 的 Claude Code marketplace,涵蓋 Gitea findings 修復/PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步與 action / Dockerfile 標準化。",
"pluginSummary": "JSC 程式碼工作流 skills:Gitea AI review findings 修復與 PR 流程、Gitea 議題 TODO 彙整與逐項實作、C# / .NET NuGet 套件更新、Gitea 專案批次同步、action / Dockerfile 標準化。"
},
"_driftDecisions": [
"決議1:skills 欄位統一為不帶尾斜線的 './skills'。現況:root plugin.json 為 './skills/'(帶尾斜線),.claude-plugin 與 .codex-plugin 已是 './skills';skillsPath 已定為統一值,root 需修正。",
"決議2:root plugin.json 已有「於 Antigravity 以 /jsc-code: 前綴呼叫」句尾,符合決議,不需調整。",
"決議3:.claude-plugin/plugin.json 助理括號清單現況為「(Claude Code / Codex / Antigravity / OpenCode)」——注意這不只是順序或命名問題,而是**完全遺漏「GitHub Copilot」這一項**(只有 4 項,doc 是 5 項且順序相同只差命名)。已依統一決議補齊為「(Claude Code / Codex / Antigravity / OpenCode / GitHub Copilot CLI)」五項。",
"決議4對應:.codex-plugin/plugin.json 無專屬補充資訊,codexNote 為空字串。"
]
}
+11 -28
View File
@@ -17,16 +17,11 @@ argument-hint: "[--action-dir <action 根目錄>] [--manifest <action.yml 路徑
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-action-params`、`spec-ask-user`、`spec-action-scaffold`、`spec-time-log`、`spec-doc-funcs-handoff`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認、不擴及無關檔案。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard`/`clean`)、`git mv` 保留歷史。
- `/jsc-shared:spec-action-params`:action 參數來源優先序(context → 經同意新增 `inputs`)、`secrets`/`vars` 一律視為不可用。
- `/jsc-shared:spec-time-log`:更新時間 Asia/Taipei `yyyy/MM/dd HH:mm:ss`、完成後統一同步時間戳。
- `/jsc-shared:spec-doc-funcs-handoff`:最終階段完整執行 `/jsc-doc:funcs` 的標準流程。
本 skill 特有補充: 本 skill 特有補充:
@@ -38,7 +33,7 @@ argument-hint: "[--action-dir <action 根目錄>] [--manifest <action.yml 路徑
## 參數來源優先序(開發中需要新參數時) ## 參數來源優先序(開發中需要新參數時)
從零建立(A1a)、對齊 composite(階段 B)、注入橫幅 step(階段 C)或補強被引用腳本的過程中,若需要新的參數值,一律依 `/jsc-shared:spec-action-params` 的優先序處理:composite 的 `runs.steps` 內優先取 `${{ gitea.* }}`/`${{ github.* }}` context(建議寫 `github.*` 以相容兩邊;`run` 腳本內可改讀 `$GITHUB_*` 環境變數),取不到才以 `AskUserQuestion` 經使用者同意新增 `inputs`;`secrets`/`vars` 一律視為不可用,需要時宣告為 `input` 由呼叫端 workflow 傳入(範例見該 spec)。 從零建立(A1a)、對齊 composite(階段 B)、注入橫幅 step(階段 C)或補強被引用腳本的過程中,若需要新的參數值,依 `/jsc-shared:spec-action-params` 執行;composite 情境下優先取的是 `${{ gitea.* }}`/`${{ github.* }}` context(`run` 腳本內對應改讀 `$GITHUB_*` 環境變數)。
--- ---
@@ -57,17 +52,11 @@ argument-hint: "[--action-dir <action 根目錄>] [--manifest <action.yml 路徑
- 帶 `--action-dir` → 採用(展開 `~`);省略 → 用目前工作目錄。 - 帶 `--action-dir` → 採用(展開 `~`);省略 → 用目前工作目錄。
- manifest:帶 `--manifest` → 採用;否則於根目錄找 `action.yml`,再退而 `action.yaml`。 - manifest:帶 `--manifest` → 採用;否則於根目錄找 `action.yml`,再退而 `action.yaml`。
- **找不到** action manifest → **不臆測**、不逕自動工;以 `AskUserQuestion` 詢問使用者要「**從零建立**新的 composite action」還是「提供正確的 action 路徑」:選「從零建立」→ 進入 A1a;選「提供路徑」→ 依新路徑重跑 A1。 - **找不到** action manifest → 依 `/jsc-shared:spec-action-scaffold` 詢問使用者要「**從零建立**新的 composite action」還是「提供正確的 action 路徑」:選「從零建立」→ 進入 A1a;選「提供路徑」→ 依新路徑重跑 A1。
### A1a. 從零建立 composite action(問答式) ### A1a. 從零建立 composite action(問答式)
依序以問答收集需求,再產生 manifest: 依 `/jsc-shared:spec-action-scaffold` 的三題骨架(名稱/用途、輸入輸出、執行目標)收集需求,於 action 根目錄產生 `action.yml`(`runs.using: composite`,含收集到的 `name`/`description`/`inputs`/`outputs`),並依執行目標以 composite steps 實作(開發中需要新參數時套用「參數來源優先序」);完成後接續 A2 往後流程(A3 必為 composite,階段 B 走「已是 composite」分支)。
1. **action 名稱**:用於 `action.yml` 的 `name`。
2. **輸入與輸出參數**:逐一收集 `inputs`/`outputs` 的名稱與 `description`(description 盡量繁體中文、無亂碼)、`required`/`default`;沒有可留空。
3. **執行目標**:詢問此 action 要達成什麼,整理濃縮成一句話作為 `action.yml` 的 `description`(盡量繁體中文、無亂碼)。
收集完成後,於 action 根目錄產生 `action.yml`(`runs.using: composite`,含收集到的 `name`/`description`/`inputs`/`outputs`),並依執行目標以 composite steps 實作(開發中需要新參數時套用「參數來源優先序」);完成後接續 A2 往後流程(A3 必為 composite,階段 B 走「已是 composite」分支)。
### A2. 讀取 action 名稱與用途 ### A2. 讀取 action 名稱與用途
@@ -102,7 +91,7 @@ argument-hint: "[--action-dir <action 根目錄>] [--manifest <action.yml 路徑
### B2. 非 composite(需對齊) ### B2. 非 composite(需對齊)
這是**破壞性高風險決策**:先以 `AskUserQuestion` 向使用者確認,選項至少含「對齊為 composite」「維持原樣只做文件化(略過對齊)」「改用 action-docker」「其他」。經確認「對齊為 composite」後才改寫: 這屬於 `/jsc-shared:spec-ask-user` 定義的破壞性決策,須先以 `AskUserQuestion` 向使用者確認,選項至少含「對齊為 composite」「維持原樣只做文件化(略過對齊)」「改用 action-docker」「其他」。經確認「對齊為 composite」後才改寫:
- 將 `runs` 保守對齊為 composite,把原執行入口轉為 composite step: - 將 `runs` 保守對齊為 composite,把原執行入口轉為 composite step:
- 原 JS action(`runs.using: node*`,`runs.main: <x>.js`)→ 轉為一個 `run` step,於 step 內以 `node <x>.js` 執行(`shell: bash`)。composite step **不會自動注入 `INPUT_*` 環境變數、也不會自動轉接 outputs**,必須手動補齊兩段映射(見下方範例): - 原 JS action(`runs.using: node*`,`runs.main: <x>.js`)→ 轉為一個 `run` step,於 step 內以 `node <x>.js` 執行(`shell: bash`)。composite step **不會自動注入 `INPUT_*` 環境變數、也不會自動轉接 outputs**,必須手動補齊兩段映射(見下方範例):
@@ -142,7 +131,7 @@ runs:
在 `runs.steps` **最前面**插入(或更新)一個輸出橫幅的 step,讓 composite action 一啟動就**輸出 action 名稱、用途、更新時間**;並於 `action.yml` 開頭補用途/更新時間註解區塊。 在 `runs.steps` **最前面**插入(或更新)一個輸出橫幅的 step,讓 composite action 一啟動就**輸出 action 名稱、用途、更新時間**;並於 `action.yml` 開頭補用途/更新時間註解區塊。
- **更新時間**依 `/jsc-shared:spec-time-log`(Asia/Taipei、固定 `yyyy/MM/dd HH:mm:ss`、寫成檔內固定字串)。本階段先寫入暫定時間戳,**階段 D 完成後會統一同步各處時間戳**(見階段 D)。 - **更新時間**依 `/jsc-shared:spec-time-log` 處理,本階段先寫入暫定時間戳,**階段 D 完成後會統一同步各處時間戳**(見階段 D)。
- 名稱/用途取自階段 A2 的 `action.yml`(缺漏時以「(未提供)」標示)。 - 名稱/用途取自階段 A2 的 `action.yml`(缺漏時以「(未提供)」標示)。
- 橫幅 step 須有可辨識的 `name`(如 `顯示 action 資訊`)與 `shell: bash`;其後緊接既有/對齊後的 steps,**不更動既有 steps**。 - 橫幅 step 須有可辨識的 `name`(如 `顯示 action 資訊`)與 `shell: bash`;其後緊接既有/對齊後的 steps,**不更動既有 steps**。
- 若已存在本 skill 先前插入的橫幅 step(依 `name` 辨識),則**更新**其內容與更新時間,不重複插入。 - 若已存在本 skill 先前插入的橫幅 step(依 `name` 辨識),則**更新**其內容與更新時間,不重複插入。
@@ -178,7 +167,7 @@ runs:
## 階段 D:完整執行 /jsc-doc:funcs 處理流程 ## 階段 D:完整執行 /jsc-doc:funcs 處理流程
標準化完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**完整執行 `/jsc-doc:funcs` 流程(前置可用性檢查、完整流程、由使用者裁示實作方式、重建 README)。完成後依該 spec **統一時間戳**:回頭同步橫幅 step 內文、`action.yml` 開頭註解區塊與 README 的更新時間,確保各處一致。 標準化完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**執行;完成後統一同步橫幅 step 內文、`action.yml` 開頭註解區塊與 README 的更新時間,確保各處一致。
--- ---
@@ -196,10 +185,4 @@ runs:
## 呼叫方式 ## 呼叫方式
格式:`[--action-dir <action 根目錄>] [--manifest <action.yml 路徑>]` — 全部可省略(根目錄預設目前工作目錄;manifest 自動尋找)。 各助理的呼叫方式依 `/jsc-shared:spec-skill-invocation`;本 skill 參數格式:`[--action-dir <action 根目錄>] [--manifest <action.yml 路徑>]` — 全部可省略(根目錄預設目前工作目錄;manifest 自動尋找)。例:`/jsc-code:action-composite --action-dir ~/work/my-action`(Codex 對應 `$action-composite --action-dir ~/work/my-action`)。
| 助理 | 呼叫 |
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:action-composite`,或 `/jsc-code:action-composite --action-dir ~/work/my-action` |
| Codex | `$action-composite`,或 `$action-composite --action-dir ~/work/my-action`,或用 `/skills` 選單 |
| OpenCode | 描述需求(如「把這個 composite action 標準化,在 steps 最前面加一個會輸出 action 名稱/用途/更新時間的 step,最後跑 funcs 補文件並重建 README」)自動觸發 |
+22 -102
View File
@@ -18,17 +18,11 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node tag>] [-
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-action-params`、`spec-action-scaffold`、`spec-ask-user`、`spec-node-src-layout`、`spec-time-log`、`spec-dockerfile`、`spec-doc-funcs-handoff`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認、不擴及無關檔案。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard`/`clean`)、`git mv` 保留歷史。
- `/jsc-shared:spec-action-params`:action 參數來源優先序(環境變數 → 經同意新增 `inputs`)、`secrets`/`vars` 一律視為不可用。
- `/jsc-shared:spec-time-log`:更新時間 Asia/Taipei `yyyy/MM/dd HH:mm:ss`、完成後統一同步時間戳。
- `/jsc-shared:spec-dockerfile`:Dockerfile 六步流程、多階段建置、固定版號、自我檢查。
- `/jsc-shared:spec-doc-funcs-handoff`:最終階段完整執行 `/jsc-doc:funcs` 的標準流程。
本 skill 特有補充: 本 skill 特有補充:
@@ -40,7 +34,7 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node tag>] [-
## 參數來源優先序(開發中需要新參數時) ## 參數來源優先序(開發中需要新參數時)
從零建立(A1a)、主程式 Node 化(階段 B)、產生 `entrypoint.sh`(階段 C)或產生 `Dockerfile`(階段 D)的過程中,若需要新的參數值,一律依 `/jsc-shared:spec-action-params` 的優先序處理:Docker 容器 action 優先取 runner 注入的執行期環境變數(Node 主程式讀 `process.env.GITHUB_*`、`entrypoint.sh` 讀 `$GITHUB_*`;Gitea 亦提供 `GITEA_*` 同義變數,建議讀 `GITHUB_*` 以相容兩邊),取不到才以 `AskUserQuestion` 經使用者同意新增 `inputs`(容器內以 `INPUT_<大寫名稱>` 取用);`secrets`/`vars` 一律視為不可用,需要時宣告為 `input` 由呼叫端 workflow 傳入(範例見該 spec)。 從零建立(A1a)、主程式 Node 化(階段 B)、產生 `entrypoint.sh`(階段 C)或產生 `Dockerfile`(階段 D)的過程中,若需要新的參數值,依 `/jsc-shared:spec-action-params` 執行(Docker 容器 action 讀取 runner 注入的執行期環境變數)。
--- ---
@@ -58,19 +52,11 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node tag>] [-
### A1. 決定 action 根目錄 ### A1. 決定 action 根目錄
- 帶 `--action-dir` → 採用(展開 `~`)。 依 `/jsc-shared:spec-action-scaffold` 判斷 action 根目錄;找不到 action manifest 時進入 A1a 問答式從零建立。
- 省略 → 用目前工作目錄。
- 根目錄須存在 `action.yml` 或 `action.yaml`(action manifest)。**找不到** action manifest → **不臆測**、不逕自動工;以 `AskUserQuestion` 詢問使用者要「**從零建立**新的 Docker 容器 action」還是「提供正確的 action 路徑」:選「從零建立」→ 進入 A1a;選「提供路徑」→ 依新路徑重跑 A1。
### A1a. 從零建立 Docker 容器 action(問答式) ### A1a. 從零建立 Docker 容器 action(問答式)
依序以問答收集需求,再產生 manifest 與主程式骨架: 依 `/jsc-shared:spec-action-scaffold` 的三題問答骨架收集需求。收集完成後,於 action 根目錄產生:`action.yml`(`runs.using: docker`、`image: Dockerfile`,含收集到的 `name`/`description`/`inputs`/`outputs`)與 `src/index.js` 主程式(依執行目標以 Node 實作,輸入以 `process.env.INPUT_<NAME>` 讀取,開發中需要新參數時套用「參數來源優先序」);完成後接續 A2 往後流程(A3 判定為 Node,階段 B 走「已是 Node」分支,階段 C/D 照常產生 `entrypoint.sh` 與 `Dockerfile`)。
1. **action 名稱**:用於 `action.yml` 的 `name`。
2. **輸入與輸出參數**:逐一收集 `inputs`/`outputs` 的名稱與 `description`(description 盡量繁體中文、無亂碼)、`required`/`default`;沒有可留空。
3. **執行目標**:詢問此 action 要達成什麼,整理濃縮成一句話作為 `action.yml` 的 `description`(盡量繁體中文、無亂碼)。
收集完成後,於 action 根目錄產生:`action.yml`(`runs.using: docker`、`image: Dockerfile`,含收集到的 `name`/`description`/`inputs`/`outputs`)與 `src/index.js` 主程式(依執行目標以 Node 實作,輸入以 `process.env.INPUT_<NAME>` 讀取,開發中需要新參數時套用「參數來源優先序」);完成後接續 A2 往後流程(A3 判定為 Node,階段 B 走「已是 Node」分支,階段 C/D 照常產生 `entrypoint.sh` 與 `Dockerfile`)。
### A2. 讀取 action 名稱與用途 ### A2. 讀取 action 名稱與用途
@@ -100,7 +86,7 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node tag>] [-
### B1. 主程式 Node 化 ### B1. 主程式 Node 化
- **已是 Node** → 確認入口檔,不改寫邏輯,直接進 B2。 - **已是 Node** → 確認入口檔,不改寫邏輯,直接進 B2。
- **非 Node(shell/python/其他)** → 這是**破壞性高風險決策**:先以 `AskUserQuestion` 向使用者確認是否改寫為 Node,選項至少含「改寫為 Node」「維持原樣只做容器化(略過改寫)」「其他」。經確認後才改寫: - **非 Node(shell/python/其他)** → 依 `/jsc-shared:spec-ask-user` 的破壞性決策規則,先以 `AskUserQuestion` 向使用者確認是否改寫為 Node,選項至少含「改寫為 Node」「維持原樣只做容器化(略過改寫)」「其他」。經確認後才改寫:
- 逐段把原主程式邏輯**保守翻譯**為 Node(建議 `index.js`);保留對外行為、輸入(環境變數/`INPUT_*`/args)與輸出(stdout/exit code/檔案副作用)契約。 - 逐段把原主程式邏輯**保守翻譯**為 Node(建議 `index.js`);保留對外行為、輸入(環境變數/`INPUT_*`/args)與輸出(stdout/exit code/檔案副作用)契約。
- 外部指令呼叫以 `child_process`(`execFileSync`/`spawnSync`)對應;檔案操作以 `fs`;環境變數以 `process.env`。 - 外部指令呼叫以 `child_process`(`execFileSync`/`spawnSync`)對應;檔案操作以 `fs`;環境變數以 `process.env`。
- 任何無法可靠等價翻譯處,**不臆測**:以 `// 需人工確認:...` 標註並回報。 - 任何無法可靠等價翻譯處,**不臆測**:以 `// 需人工確認:...` 標註並回報。
@@ -108,14 +94,7 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node tag>] [-
### B2. 主程式依賴鏈的 `.js` 集中到 `src/` ### B2. 主程式依賴鏈的 `.js` 集中到 `src/`
- 在 action 根目錄建立 `src/`(若不存在)。 依 `/jsc-shared:spec-node-src-layout` 把主程式入口及其依賴鏈的 `.js`/`.mjs`/`.cjs` 收進 `src/`,並同步更新所有引用。`action.yml`/`Dockerfile`/`entrypoint.sh` 內對主程式路徑的引用亦需一併更新;若 `src/` 下無 `package.json` 而專案需要相依,於 `src/` 建立或移入 `package.json`,`main` 指向入口檔。
- 將**主程式入口及其 `require`/`import` 依賴鏈**的 `.js`/`.mjs`/`.cjs`(排除 `node_modules`/`.git`/`.docs`/`bin`/`obj`/第三方依賴)**移入 `src/`**,優先 `git mv` 保留歷史。主程式入口統一為 `src/index.js`(或 `src/<main>.js`)。
- **明文排除、不搬**:外部工具依慣例路徑尋找的檔案——`*.config.js`(`eslint.config.js`/`jest.config.js`/`webpack.config.js` 等)、`.*rc.js`、husky/commitlint 等工具設定,以及 `test/`/`tests/`/`scripts/` 目錄;搬走會弄壞 lint/test/build 流程。
- **移動後必須更新所有引用**,確保不破壞:
- 模組間的 `require`/`import` 相對路徑。
- `package.json` 的 `main`/`bin`/`scripts`/`exports`(指向 `src/...`)。
- `action.yml`/`Dockerfile`/`entrypoint.sh` 內對主程式路徑的引用。
- 若 `src/` 下無 `package.json` 而專案需要相依,於 `src/` 建立或移入 `package.json`,`main` 指向入口檔;不擅自新增與功能無關的相依。
### B3. action.yml 對齊 docker ### B3. action.yml 對齊 docker
@@ -139,7 +118,7 @@ runs:
在 action 根目錄產生(或覆寫)`entrypoint.sh`:**啟動 node 主程式前,先輸出 action 名稱、用途、更新時間**。 在 action 根目錄產生(或覆寫)`entrypoint.sh`:**啟動 node 主程式前,先輸出 action 名稱、用途、更新時間**。
- **更新時間**依 `/jsc-shared:spec-time-log`(Asia/Taipei、固定 `yyyy/MM/dd HH:mm:ss`、寫成檔內固定字串)。本階段先寫入暫定時間戳,**階段 E 完成後會統一同步各處時間戳**(見階段 E)。 - **更新時間**依 `/jsc-shared:spec-time-log` 執行;本階段先寫入暫定時間戳,**階段 E 完成後會統一同步各處時間戳**(見階段 E)。
- 名稱/用途取自階段 A2 的 `action.yml`。 - 名稱/用途取自階段 A2 的 `action.yml`。
- 最後以 `exec node <主程式>`(如 `exec node /action/src/index.js "$@"`)啟動,讓 node 取代 shell 行程,正確傳遞訊號與 exit code;路徑須與階段 D 的 `Dockerfile` 落點一致。 - 最後以 `exec node <主程式>`(如 `exec node /action/src/index.js "$@"`)啟動,讓 node 取代 shell 行程,正確傳遞訊號與 exit code;路徑須與階段 D 的 `Dockerfile` 落點一致。
- **若階段 B 裁示「維持非 Node」**:橫幅照常輸出,最後改以 `exec <原直譯器> <主程式>` 啟動(如 `exec python3 /action/src/main.py "$@"`、`exec sh /action/src/main.sh "$@"`)。 - **若階段 B 裁示「維持非 Node」**:橫幅照常輸出,最後改以 `exec <原直譯器> <主程式>` 啟動(如 `exec python3 /action/src/main.py "$@"`、`exec sh /action/src/main.sh "$@"`)。
@@ -166,80 +145,21 @@ exec node /action/src/index.js "$@"
--- ---
## 階段 D:產生 Dockerfile(使用最新 LTS node 版本,六步流程依主程式實作量身產生) ## 階段 D:產生 Dockerfile(使用最新 LTS node 版本)
在 action 根目錄產生(或覆寫)`Dockerfile`,**base image 使用最新 LTS node 版本(固定 major tag)**,採**多階段建置**以縮小最終映像檔。六步流程是**骨架**,但**每一步的實際內容必須依階段 B 的主程式(`src/`)實作決定**——不要套用與主程式無關的固定樣板,也不要硬塞主程式用不到的安裝或建置指令。 先檢視 `src/` 主程式與 `package.json`(相依與 lockfile、是否需要 build/transpile、OS 層相依、執行期需求、入口檔),依盤點結果依 `/jsc-shared:spec-dockerfile` 的六步流程(參數處理 → 安裝套件 → 複製檔案 → 執行程序 → 縮小映像檔 → 設定入口)產生(或覆寫)action 根目錄的 `Dockerfile`;並產生(或補齊)`.dockerignore`,至少排除 `.git`、`.docs`、`node_modules`(宿主端,可能含平台不符的原生模組)。
### D0. 先盤點主程式實作需求 本 skill 特有差異:
產生 Dockerfile 前,先檢視 `src/` 主程式與 `package.json`,判斷各步該放什麼: - **base image 預設 Node LTS**:預設使用產生當下的最新 LTS major 固定 tag(如 build 基底 `node:22`、runtime 基底 `node:22-slim`,可查詢 `https://hub.docker.com/_/node` 確認版號),帶 `--node-version <tag>` 時改用 `node:<tag>`/`node:<tag>-slim`。**若階段 B 裁示「維持非 Node」**:base image 改用對應語言官方映像(如 `python:3-slim`),第 2 步改安裝該生態相依(如 `pip install -r requirements.txt`),其餘六步結構不變;`ENTRYPOINT` 仍指向 `entrypoint.sh`(其內 `exec` 原直譯器,見階段 C)。
- **入口一致性自我檢查**(補充於 spec-dockerfile 通用自我檢查之外):`entrypoint.sh` 內 `node <主程式>` 路徑(如 `/action/src/index.js`)須與 `Dockerfile` 的 `WORKDIR`/`COPY` 落點一致,避免容器啟動時找不到主程式;`action.yml` 的 `runs.entrypoint` 若存在,必須是**絕對路徑**且與 `Dockerfile` 落點一致(預設不設,讓 `Dockerfile` 的 `ENTRYPOINT` 生效)。
- **相依套件**:`src/package.json` 的 `dependencies`/`devDependencies`、是否有 lockfile(`package-lock.json`/`npm-shrinkwrap.json`)。決定第 2 步用 `npm ci` 還是 `npm install`、是否需要 dev 相依來 build。
- **是否需要 build/transpile**:有無 `scripts.build`、是否為 TypeScript/打包(產物落在 `dist/`/`build/` 等)。決定第 4 步是否要跑建置、第 5 步 runtime 該帶入哪個產物目錄。
- **OS 層相依**:主程式是否以 `child_process` 呼叫外部執行檔(如 `git`、`bash`、`openssl`、`python3`、`ffmpeg`、`imagemagick`),或使用需編譯的原生模組(node-gyp/native addon)。決定第 2 步是否要 `apt-get install` 對應套件,以及第 5 步 runtime 基底是否需保留這些執行檔。
- **執行期需求**:主程式讀取的環境變數、需存取的檔案路徑、是否寫入暫存目錄。決定 `WORKDIR`、需 `COPY` 的檔案範圍與必要的 `ENV`。
- **入口檔**:階段 B 決定的主程式入口(如 `src/index.js`),決定第 6 步與 `entrypoint.sh` 內 `node` 路徑。
- **產生 `.dockerignore`**:於 action 根目錄產生(或補齊)`.dockerignore`,至少排除 `.git`、`.docs`、`node_modules`(宿主端,可能含平台不符的原生模組)與其他非執行必需檔案,避免帶進 build context。
### D1~D6:依盤點結果填入六步
1. **參數處理**:以 `ARG` 注入 node 版本(預設最新)與主程式需要的 build-time 參數;可調參數集中在檔案開頭。
2. **安裝套件**:依 D0 的相依結果安裝——有 lockfile 用 `npm ci`、否則 `npm install`,**二擇一寫死於 Dockerfile**(不得以 `npm ci || npm install` 之類 fallback 串接,避免靜默吞錯、破壞 lockfile 的可重現性);不需 build 時加 `--omit=dev`,需 build 時先裝完整相依(含 dev)供第 4 步使用。**只在主程式真的會用到時**才 `apt-get install` OS 套件,並於同一 `RUN` 清掉 apt 快取。先 `COPY` 相依描述(`package*.json`)再安裝,以利 layer 快取。
3. **複製檔案**:`COPY` 主程式實際需要的檔案(`src/`、`entrypoint.sh`,及主程式會讀取的資源),**明列路徑、不整包 `COPY .`**,並配合 `.dockerignore` 排除非必要檔案。
4. **執行程序**:賦予 `entrypoint.sh` 執行權限;**僅當 D0 判定需要 build/transpile 時**才執行(如 `npm run build`),否則此步只做權限設定,不要硬加建置指令。
5. **縮小映像檔**:多階段建置,runtime 改用 slim 基底,只 `COPY --from=build` 帶入主程式執行**真正需要**的產物——有 build 產物時帶入 `dist/` 與 production `node_modules`(`npm prune --omit=dev` 或重裝 production 相依),純 JS 無 build 時帶入 `src/` 與 production `node_modules`;若 D0 判定 runtime 需要某些 OS 執行檔,於 runtime 階段一併安裝(slim 基底可能不含)。不要把 build 階段的 dev 相依與快取帶進最終映像;**`COPY --from=build` 必須逐項明列路徑,不得整包 `COPY --from=build /action /action`**(整包搬等於沒有縮小)。
6. **設定入口**:`ENTRYPOINT` 指向 `entrypoint.sh`,其內 `node` 路徑為 D0 的入口檔。
base image 版本依 `/jsc-shared:spec-dockerfile`(固定 major tag、不用 `latest`):**預設使用產生當下的最新 LTS major 固定 tag**(如 build 基底 `node:22`、runtime 基底 `node:22-slim`),可查詢 Docker Hub(`https://hub.docker.com/_/node`)確認當前 LTS 版號;帶 `--node-version <tag>` 時改用 `node:<tag>` 與 `node:<tag>-slim`。
下列為**最簡基準骨架**(純 JS、無 build、無 OS 相依的情況);實際內容須依 D0 盤點調整,第 4 步建置、第 2/5 步 OS 套件等視主程式需要增刪:
```Dockerfile
# 1. 參數處理:node 版本以 ARG 注入(預設當前最新 LTS major,範例為 22;--node-version 可覆寫)
ARG NODE_VERSION=22
ARG NODE_RUNTIME=22-slim
# ---- build 階段:安裝相依、準備產物 ----
FROM node:${NODE_VERSION} AS build
WORKDIR /action
# 2. 安裝套件:先帶相依描述以利 layer 快取,再裝 src/ 相依
# (依 D0 盤點二擇一寫死:有 lockfile 用 npm ci、否則 npm install,不用 || 串接;
# 需 build 時改裝完整相依;需 OS 套件時於此 apt-get install 並清快取)
COPY src/package*.json /action/src/
RUN if [ -f /action/src/package.json ]; then \
cd /action/src && npm ci --omit=dev; \
fi
# 3. 複製檔案:明列主程式實際需要的檔案(配合 .dockerignore,不整包 COPY .)
COPY src/ /action/src/
COPY entrypoint.sh /action/entrypoint.sh
# 4. 執行程序:賦予 entrypoint 執行權限(D0 判定需要 build 時於此 npm run build)
RUN chmod +x /action/entrypoint.sh
# 5. 縮小映像檔:多階段建置,runtime 改用 slim 基底,逐項明列只帶執行真正需要的產物
# (有 build 產物時改帶 dist/ 與 production node_modules;runtime 需要的 OS 執行檔於此安裝)
FROM node:${NODE_RUNTIME} AS runtime
WORKDIR /action
COPY --from=build /action/src /action/src
COPY --from=build /action/entrypoint.sh /action/entrypoint.sh
# 6. 設定入口:以 entrypoint.sh 啟動(其內 node 指向 D0 的入口檔)
ENTRYPOINT ["/action/entrypoint.sh"]
```
- **依主程式實作**:上面只是基準骨架。若主程式是 TypeScript/需打包,第 4 步要加 `npm run build`、第 5 步只帶 `dist/` 與 production 相依;若主程式會 spawn 外部執行檔或用原生模組,第 2/5 步要補對應 OS 套件;用不到的步驟內容不要硬塞。
- **若階段 B 裁示「維持非 Node」**:base image 改用對應語言官方映像(如 `python:3-slim`),第 2 步改安裝該生態相依(如 `pip install -r requirements.txt`),其餘六步結構不變;`ENTRYPOINT` 仍指向 `entrypoint.sh`(其內 `exec` 原直譯器,見階段 C)。
- **版號對齊**:依 `/jsc-shared:spec-dockerfile`(runtime 與 build 版號一致、以獨立 runtime ARG 帶入不用 `${VERSION}-slim` 組裝、`-alpine`/`-slim` 變體沿用同一 tag、使用者要求 `latest` 時 runtime 用 `node:slim`)。
- **自我檢查**:`entrypoint.sh` 的 `node <主程式>` 路徑(如 `/action/src/index.js`)需與 `Dockerfile` 的 `WORKDIR`/`COPY` 落點一致,避免容器啟動時找不到主程式;`action.yml` 的 `runs.entrypoint` 若存在,必須是**絕對路徑**且與 `Dockerfile` 落點一致(預設不設,讓 Dockerfile 的 `ENTRYPOINT` 生效)。
- 此檔的用途/更新日期註解與逐行註解,同樣於階段 E 由 funcs 指令檔流程統一補齊。 - 此檔的用途/更新日期註解與逐行註解,同樣於階段 E 由 funcs 指令檔流程統一補齊。
--- ---
## 階段 E:完整執行 /jsc-doc:funcs 處理流程 ## 階段 E:完整執行 /jsc-doc:funcs 處理流程
容器化與 Node 化完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**完整執行 `/jsc-doc:funcs` 流程(前置可用性檢查、完整流程、由使用者裁示實作方式、重建 README;本次新增/變更的 `entrypoint.sh`、`Dockerfile` 與 `src/` 內 Node 主程式都會被涵蓋)。完成後依該 spec **統一時間戳**:回頭同步 `entrypoint.sh` 橫幅輸出與註解區塊、`Dockerfile` 註解區塊與 README 的更新時間,確保各處一致。 容器化與 Node 化完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**執行 `/jsc-doc:funcs` 流程(本次新增/變更的 `entrypoint.sh`、`Dockerfile` 與 `src/` 內 Node 主程式都會被涵蓋);完成後依該 spec 統一時間戳,回頭同步 `entrypoint.sh` 橫幅輸出、`Dockerfile` 註解區塊與 README 的更新時間。
--- ---
@@ -257,10 +177,10 @@ ENTRYPOINT ["/action/entrypoint.sh"]
## 呼叫方式 ## 呼叫方式
格式:`[--action-dir <action 根目錄>] [--node-version <node tag>] [--main <主程式檔>]` — 全部可省略(根目錄預設目前工作目錄;node 版本預設最新 LTS;主程式自動判斷)。 依 `/jsc-shared:spec-skill-invocation`;本 skill 的 `<name>` 為 `action-docker`,參數格式:`[--action-dir <action 根目錄>] [--node-version <node tag>] [--main <主程式檔>]` — 全部可省略(根目錄預設目前工作目錄;node 版本預設最新 LTS;主程式自動判斷)。
| 助理 | 呼叫 | 範例:
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:action-docker`,或 `/jsc-code:action-docker --action-dir ~/work/my-action --node-version 22` | - Claude Code / Antigravity:`/jsc-code:action-docker`,或 `/jsc-code:action-docker --action-dir ~/work/my-action --node-version 22`
| Codex | `$action-docker`,或 `$action-docker --action-dir ~/work/my-action`,或用 `/skills` 選單 | - Codex:`$action-docker`,或 `$action-docker --action-dir ~/work/my-action`,或用 `/skills` 選單
| OpenCode | 描述需求(如「把這個 action 主程式改成 node、主程式相關 js 收進 src/,產生會先輸出 action 名稱/用途/更新時間再啟動 node 的 entrypoint.sh、用最新 LTS node 版本的 Dockerfile,最後跑 funcs 補文件並重建 README」)自動觸發 | - OpenCode:描述需求(如「把這個 action 主程式改成 node、主程式相關 js 收進 src/,產生會先輸出 action 名稱/用途/更新時間再啟動 node 的 entrypoint.sh、用最新 LTS node 版本的 Dockerfile,最後跑 funcs 補文件並重建 README」)自動觸發
+17 -35
View File
@@ -18,16 +18,11 @@ argument-hint: "[--action-dir <action 根目錄>] [--node-version <node runtime
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-action-scaffold`、`spec-ask-user`、`spec-node-src-layout`、`spec-action-params`、`spec-time-log`、`spec-doc-funcs-handoff`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認、不擴及無關檔案。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard`/`clean`)、`git mv` 保留歷史。
- `/jsc-shared:spec-action-params`:action 參數來源優先序(環境變數 → 經同意新增 `inputs`)、`secrets`/`vars` 一律視為不可用。
- `/jsc-shared:spec-time-log`:更新時間 Asia/Taipei `yyyy/MM/dd HH:mm:ss`、完成後統一同步時間戳。
- `/jsc-shared:spec-doc-funcs-handoff`:最終階段完整執行 `/jsc-doc:funcs` 的標準流程。
本 skill 特有補充: 本 skill 特有補充:
@@ -59,7 +54,7 @@ node action 相對 docker/composite 的**關鍵差異**(全程據此處理
## 參數來源優先序(開發中需要新參數時) ## 參數來源優先序(開發中需要新參數時)
從零建立(A1a)、主程式 Node 化(階段 B)、對齊 `action.yml`(階段 C)或打包(階段 D)的過程中,若需要新的參數值,一律依 `/jsc-shared:spec-action-params` 的優先序處理:node action 主程式優先取 runner 注入的執行期環境變數(`process.env.GITHUB_*`;Gitea 亦提供 `GITEA_*` 同義變數,建議讀 `GITHUB_*` 以相容兩邊),既有 `inputs` 以 `process.env.INPUT_<大寫名稱>`(或 `core.getInput`)取用,取不到才以 `AskUserQuestion` 經使用者同意新增 `inputs`(由呼叫端 workflow 以 `with:` 傳入);`secrets`/`vars` 一律視為不可用,需要時宣告為 `input` 由呼叫端傳入(範例見該 spec)。 從零建立(A1a)、主程式 Node 化(階段 B)、對齊 `action.yml`(階段 C)或打包(階段 D)的過程中,若需要新的參數值,依 `/jsc-shared:spec-action-params` 的優先序處理;node action 主程式讀值方式:優先取 `process.env.GITHUB_*`(Gitea 亦提供 `GITEA_*` 同義變數,建議讀 `GITHUB_*` 以相容兩邊),既有 `inputs` 以 `process.env.INPUT_<大寫名稱>`(或 `core.getInput`)取用。
--- ---
@@ -77,17 +72,11 @@ node action 相對 docker/composite 的**關鍵差異**(全程據此處理
### A1. 決定 action 根目錄 ### A1. 決定 action 根目錄
- 帶 `--action-dir` → 採用(展開 `~`)。 依 `/jsc-shared:spec-action-scaffold` 判斷 action 根目錄;**找不到** action manifest 時,依該 spec 詢問使用者「從零建立」或「提供正確的 action 路徑」,選「從零建立」→ 進入 A1a。
- 省略 → 用目前工作目錄。
- 根目錄須存在 `action.yml` 或 `action.yaml`(action manifest)。**找不到** action manifest → **不臆測**、不逕自動工;以 `AskUserQuestion` 詢問使用者要「**從零建立**新的 Node action」還是「提供正確的 action 路徑」:選「從零建立」→ 進入 A1a;選「提供路徑」→ 依新路徑重跑 A1。
### A1a. 從零建立 Node action(問答式) ### A1a. 從零建立 Node action(問答式)
依序以問答收集需求,再產生 manifest 與主程式骨架: 依 `/jsc-shared:spec-action-scaffold` 的三題骨架問答收集需求(action 名稱/用途、輸入輸出、執行目標;第 2 題收集 `outputs` 時**不收集 `value`**)。
1. **action 名稱**:用於 `action.yml` 的 `name`。
2. **輸入與輸出參數**:逐一收集 `inputs`(名稱、`description`、`required`、`default`)與 `outputs`(名稱、`description`;**不收集 `value`**);description 盡量繁體中文、無亂碼;沒有可留空。
3. **執行目標**:詢問此 action 要達成什麼,整理濃縮成一句話作為 `action.yml` 的 `description`(盡量繁體中文、無亂碼)。
收集完成後,於 action 根目錄產生:`action.yml`(`runs.using: node24`、`runs.main: src/index.js`,含收集到的 `name`/`description`/`inputs`/`outputs`)與 `src/index.js` 主程式(依執行目標以 Node 實作,輸入以 `process.env.INPUT_<NAME>` 讀取、輸出以附加寫入 `process.env.GITHUB_OUTPUT`,開發中需要新參數時套用「參數來源優先序」);預設走**零相依**路線。完成後接續 A2 往後流程(A3 判定為 Node,階段 B 走「已是 Node」分支,階段 C/D 照常對齊 manifest 與處理相依)。 收集完成後,於 action 根目錄產生:`action.yml`(`runs.using: node24`、`runs.main: src/index.js`,含收集到的 `name`/`description`/`inputs`/`outputs`)與 `src/index.js` 主程式(依執行目標以 Node 實作,輸入以 `process.env.INPUT_<NAME>` 讀取、輸出以附加寫入 `process.env.GITHUB_OUTPUT`,開發中需要新參數時套用「參數來源優先序」);預設走**零相依**路線。完成後接續 A2 往後流程(A3 判定為 Node,階段 B 走「已是 Node」分支,階段 C/D 照常對齊 manifest 與處理相依)。
@@ -121,7 +110,7 @@ node action 相對 docker/composite 的**關鍵差異**(全程據此處理
### B1. 主程式 Node 化 ### B1. 主程式 Node 化
- **已是 Node** → 確認入口檔,不改寫邏輯,直接進 B2。 - **已是 Node** → 確認入口檔,不改寫邏輯,直接進 B2。
- **非 Node(shell/python/其他)** → 這是**破壞性高風險決策**:先以 `AskUserQuestion` 向使用者確認是否改寫為 Node,選項至少含「改寫為 Node」「改用 action-docker(維持原語言容器化)」「其他」。經確認後才改寫: - **非 Node(shell/python/其他)** → 這是**破壞性高風險決策**,依 `/jsc-shared:spec-ask-user` 以 `AskUserQuestion` 向使用者確認是否改寫為 Node,選項至少含「改寫為 Node」「改用 action-docker(維持原語言容器化)」「其他」。經確認後才改寫:
- 逐段把原主程式邏輯**保守翻譯**為 Node(建議 `src/index.js`);保留對外行為、輸入(環境變數/`INPUT_*`/args)與輸出(`$GITHUB_OUTPUT`/stdout/exit code/檔案副作用)契約。 - 逐段把原主程式邏輯**保守翻譯**為 Node(建議 `src/index.js`);保留對外行為、輸入(環境變數/`INPUT_*`/args)與輸出(`$GITHUB_OUTPUT`/stdout/exit code/檔案副作用)契約。
- 外部指令呼叫以 `child_process`(`execFileSync`/`spawnSync`)對應;檔案操作以 `fs`;環境變數以 `process.env`。 - 外部指令呼叫以 `child_process`(`execFileSync`/`spawnSync`)對應;檔案操作以 `fs`;環境變數以 `process.env`。
- 任何無法可靠等價翻譯處,**不臆測**:以 `// 需人工確認:...` 標註並回報。 - 任何無法可靠等價翻譯處,**不臆測**:以 `// 需人工確認:...` 標註並回報。
@@ -129,14 +118,7 @@ node action 相對 docker/composite 的**關鍵差異**(全程據此處理
### B2. 主程式依賴鏈的 `.js` 集中到 `src/` ### B2. 主程式依賴鏈的 `.js` 集中到 `src/`
- 在 action 根目錄建立 `src/`(若不存在)。 依 `/jsc-shared:spec-node-src-layout` 把主程式入口及其依賴鏈的 `.js`/`.mjs`/`.cjs` 收進 `src/`,並同步更新所有引用。`package.json` 的 `scripts.build`(ncc 入口)與 `action.yml` 的 `runs.main` 先指向 `src/index.js`;若階段 D 判定需打包,再改指 `dist/index.js`。`@vercel/ncc` 屬階段 D 的 build 相依,經同意後才加,不在本階段新增。
- 將**主程式入口及其 `require`/`import` 依賴鏈**的 `.js`/`.mjs`/`.cjs`(排除 `node_modules`/`.git`/`.docs`/`dist`/第三方依賴)**移入 `src/`**,優先 `git mv` 保留歷史。主程式入口統一為 `src/index.js`(或 `src/<main>.js`)。
- **明文排除、不搬**:外部工具依慣例路徑尋找的檔案——`*.config.js`(`eslint.config.js`/`jest.config.js`/`ncc` 等工具設定)、`.*rc.js`、husky/commitlint 等工具設定,以及 `test/`/`tests/`/`scripts/` 目錄;搬走會弄壞 lint/test/build 流程。
- **移動後必須更新所有引用**,確保不破壞:
- 模組間的 `require`/`import` 相對路徑。
- `package.json` 的 `main`/`bin`/`scripts`/`exports`(指向 `src/...`;`scripts.build` 的 ncc 入口指向 `src/index.js`)。
- `action.yml` 的 `runs.main`(先指向 `src/index.js`;若階段 D 判定需打包,再改指 `dist/index.js`)。
- 若 `src/` 需要的相依尚未宣告,於 `package.json` 補上;不擅自新增與功能無關的相依(`@vercel/ncc` 屬階段 D 的 build 相依,經同意後才加)。
--- ---
@@ -162,7 +144,7 @@ runs:
node action 沒有 `entrypoint.sh`,橫幅改由**主程式 JS 最前面輸出**。在 `src/index.js`(或階段 B 決定的入口)最上方,於任何其他邏輯**之前**插入(或更新)橫幅輸出: node action 沒有 `entrypoint.sh`,橫幅改由**主程式 JS 最前面輸出**。在 `src/index.js`(或階段 B 決定的入口)最上方,於任何其他邏輯**之前**插入(或更新)橫幅輸出:
- **更新時間**依 `/jsc-shared:spec-time-log`(Asia/Taipei、固定 `yyyy/MM/dd HH:mm:ss`、寫成檔內固定字串)。本階段先寫入暫定時間戳,**階段 E 完成後會統一同步各處時間戳**(見階段 E)。 - **更新時間**依 `/jsc-shared:spec-time-log` 處理。本階段先寫入暫定時間戳,**階段 E 完成後會統一同步各處時間戳**(見階段 E)。
- 名稱/用途取自階段 A2 的 `action.yml`(缺漏時以「(未提供)」標示)。 - 名稱/用途取自階段 A2 的 `action.yml`(缺漏時以「(未提供)」標示)。
- 若已存在本 skill 先前插入的橫幅(依可辨識註解標記),則**更新**其內容與時間戳,不重複插入。 - 若已存在本 skill 先前插入的橫幅(依可辨識註解標記),則**更新**其內容與時間戳,不重複插入。
@@ -223,7 +205,7 @@ node action 的 runner **不會自動 `npm install`**,因此相依必須隨 re
## 階段 E:完整執行 /jsc-doc:funcs 處理流程 ## 階段 E:完整執行 /jsc-doc:funcs 處理流程
標準化與打包完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**完整執行 `/jsc-doc:funcs` 流程(前置可用性檢查、完整流程、由使用者裁示實作方式、重建 README;本次新增/變更的 `src/` 內 Node 主程式、`action.yml`、`package.json` 都會被涵蓋)。完成後依該 spec **統一時間戳**:回頭同步主程式橫幅輸出與註解區塊、`action.yml` 開頭註解區塊與 README 的更新時間,確保各處一致。 標準化與打包完成後,以階段 A1 的 action 根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個 action 專案**完整執行 `/jsc-doc:funcs` 流程;本次新增/變更的 `src/` 內 Node 主程式、`action.yml`、`package.json` 都會被涵蓋,完成後依該 spec 統一同步主程式橫幅輸出、`action.yml` 開頭註解區塊與 README 的更新時間。
--- ---
@@ -242,10 +224,10 @@ node action 的 runner **不會自動 `npm install`**,因此相依必須隨 re
## 呼叫方式 ## 呼叫方式
格式:`[--action-dir <action 根目錄>] [--node-version <node runtime>] [--main <主程式檔>]` — 全部可省略(根目錄預設目前工作目錄;node 版本預設 `node24`;主程式自動判斷)。 依 `/jsc-shared:spec-skill-invocation` 的呼叫方式;本 skill 為 `jsc-code:action-node`,格式:`[--action-dir <action 根目錄>] [--node-version <node runtime>] [--main <主程式檔>]` — 全部可省略(根目錄預設目前工作目錄;node 版本預設 `node24`;主程式自動判斷)。
| 助理 | 呼叫 | 範例:
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:action-node`,或 `/jsc-code:action-node --action-dir ~/work/my-action --node-version node24` | - Claude Code / Antigravity:`/jsc-code:action-node --action-dir ~/work/my-action --node-version node24`
| Codex | `$action-node`,或 `$action-node --action-dir ~/work/my-action`,或用 `/skills` 選單 | - Codex:`$action-node --action-dir ~/work/my-action`
| OpenCode | 描述需求(如「把這個 action 主程式改成 node、主程式相關 js 收進 src/,對齊 action.yml 為 node action、在主程式最前面輸出 action 名稱/用途/更新時間,有相依就用 ncc 打包成 dist 並 commit,最後跑 funcs 補文件並重建 README」)自動觸發 | - OpenCode:描述需求(如「把這個 action 主程式改成 node、主程式相關 js 收進 src/,對齊 action.yml 為 node action、在主程式最前面輸出 action 名稱/用途/更新時間,有相依就用 ncc 打包成 dist 並 commit,最後跑 funcs 補文件並重建 README」)自動觸發
+11 -58
View File
@@ -16,15 +16,11 @@ argument-hint: "[--project-dir <專案根目錄>] [--dockerfile <Dockerfile 路
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-ask-user`、`spec-dockerfile`、`spec-doc-funcs-handoff`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認、不擴及無關檔案。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard`/`clean`)。
- `/jsc-shared:spec-dockerfile`:Dockerfile 六步流程、多階段建置、對外契約不動、自我檢查。
- `/jsc-shared:spec-doc-funcs-handoff`:最終階段完整執行 `/jsc-doc:funcs` 的標準流程。
本 skill 特有補充: 本 skill 特有補充:
@@ -56,7 +52,7 @@ argument-hint: "[--project-dir <專案根目錄>] [--dockerfile <Dockerfile 路
- 帶 `--dockerfile` → 採用(相對專案根目錄;展開後須存在)。 - 帶 `--dockerfile` → 採用(相對專案根目錄;展開後須存在)。
- 省略 → 於專案根目錄與常見位置尋找 `Dockerfile`(含 `Dockerfile.*`、`*/Dockerfile`,排除 `node_modules`/`.git`/`.docs`/`bin`/`obj`/第三方依賴)。 - 省略 → 於專案根目錄與常見位置尋找 `Dockerfile`(含 `Dockerfile.*`、`*/Dockerfile`,排除 `node_modules`/`.git`/`.docs`/`bin`/`obj`/第三方依賴)。
- 恰好一個 → 直接採用。 - 恰好一個 → 直接採用。
- 多個 → 以 `AskUserQuestion` 列出讓使用者選擇要整理哪一個(或逐一整理)。 - 多個 → 依 `/jsc-shared:spec-ask-user` 以 `AskUserQuestion` 列出讓使用者選擇要整理哪一個(或逐一整理)。
- **找不到** → 回報「專案內找不到 `Dockerfile`」並詢問正確路徑;**不臆造** Dockerfile、不在沒有 Dockerfile 的專案上憑空生成(本 skill 的職責是「整理既有 Dockerfile」,產生全新 action 容器請改用 `/jsc-code:action-docker`)。 - **找不到** → 回報「專案內找不到 `Dockerfile`」並詢問正確路徑;**不臆造** Dockerfile、不在沒有 Dockerfile 的專案上憑空生成(本 skill 的職責是「整理既有 Dockerfile」,產生全新 action 容器請改用 `/jsc-code:action-docker`)。
### A3. 讀現有指令並判斷語言/生態與既有建置流程 ### A3. 讀現有指令並判斷語言/生態與既有建置流程
@@ -76,59 +72,22 @@ argument-hint: "[--project-dir <專案根目錄>] [--dockerfile <Dockerfile 路
## 階段 B:整理 Dockerfile 為六步流程 ## 階段 B:整理 Dockerfile 為六步流程
在**保留建置行為**的前提下,把目標 `Dockerfile` 的指令重整/歸位為 `/jsc-shared:spec-dockerfile` 定義的**固定六步流程**(參數處理 → 安裝套件 → 複製檔案 → 執行程序 → 縮小映像檔 → 設定入口),並採**多階段建置**縮小最終映像;各步要點(ARG 集中檔首、相依描述先 `COPY`、同層清理快取、`COPY --from` 逐項明列、搬移 runtime 需要的 `ENV`/`WORKDIR`/`EXPOSE`/`USER`)依該 spec。 在**保留建置行為**的前提下,把目標 `Dockerfile` 的指令依 `/jsc-shared:spec-dockerfile` 重整/歸位為固定六步流程並採**多階段建置**縮小最終映像;六步骨架、`ARG`/`COPY`/`COPY --from` 等要點、對外契約(`ENTRYPOINT`/`CMD`/`EXPOSE`/`ENV`/`VOLUME`/`HEALTHCHECK`/`USER`)不動、`.dockerignore`、自我檢查與建置驗證,一律依該 spec 執行。
**重整原則(關鍵)** **重整原則(本 skill 特有:職責是「整理既有檔案」而非從零產生,因此多一層等價性把關)**
- **語意等價優先**:重排指令前,先確認該調整不改變最終映像內容與建置結果。某些重排會改變行為(例如 `ARG` 的作用範圍跨 `FROM` 會失效、`COPY` 順序影響快取與覆蓋、`RUN` 之間有狀態相依、單階段改多階段時 runtime 缺少 build 期才有的檔案)。**凡無法可靠保證等價的重整,先以 `AskUserQuestion` 向使用者確認**(選項至少含「依建議重整(多階段+六步)」「只在不改變行為前提下做最小重排」「維持原結構只補註解(略過重整,交給階段 C)」「其他」),確認後才覆寫。 - **語意等價優先**:重排指令前,先確認該調整不改變最終映像內容與建置結果。某些重排會改變行為(例如 `ARG` 的作用範圍跨 `FROM` 會失效、`COPY` 順序影響快取與覆蓋、`RUN` 之間有狀態相依、單階段改多階段時 runtime 缺少 build 期才有的檔案)。**凡無法可靠保證等價的重整,依 `/jsc-shared:spec-ask-user` 先向使用者確認**(選項至少含「依建議重整(多階段+六步)」「只在不改變行為前提下做最小重排」「維持原結構只補註解(略過重整,交給階段 C)」「其他」),確認後才覆寫。
- **已是多階段** → 將既有各階段對應到六步(build 階段涵蓋 1–4、runtime 階段涵蓋 5–6),補齊缺漏的快取最佳化與清理,不破壞既有 `--from` 依賴。 - **已是多階段** → 將既有各階段對應到六步(build 階段涵蓋 1–4、runtime 階段涵蓋 5–6),補齊缺漏的快取最佳化與清理,不破壞既有 `--from` 依賴。
- **單階段改多階段** → 僅在能可靠判斷 runtime 真正需要哪些產物時才做;無法可靠判斷時,**不臆測**,標 `# 需人工確認:runtime 需要的產物清單` 並回報,退回「最小重排+補註解」。 - **單階段改多階段** → 僅在能可靠判斷 runtime 真正需要哪些產物時才做;無法可靠判斷時,**不臆測**,標 `# 需人工確認:runtime 需要的產物清單` 並回報,退回「最小重排+補註解」。
- **多階段不適用** → 若該映像本質上無法受益於多階段(例如純資料映像、最終就是要完整建置環境),保留單階段,於步驟 5 以註解說明「此映像不適用多階段縮小」,其餘步驟仍依序歸位。 - **多階段不適用** → 若該映像本質上無法受益於多階段(例如純資料映像、最終就是要完整建置環境),保留單階段,於步驟 5 以註解說明「此映像不適用多階段縮小」,其餘步驟仍依序歸位。
- **對外契約不動**:依 `/jsc-shared:spec-dockerfile`(`ENTRYPOINT`/`CMD`/`EXPOSE`/`ENV`/`VOLUME`/`HEALTHCHECK`/`USER` 語意與原檔一致,只可調整位置與分層)。
- **`.dockerignore`**:若為了「複製檔案」步驟的正確性需要排除建置產物/`node_modules`/`.git`,可建立或補強 `.dockerignore`(僅新增排除項,不刪既有),並回報。
重整骨架(實際指令、base image、語言依階段 A 偵測結果填入;非 Node 專案請換成對應語言的安裝/建置指令與 runtime 基底): 此檔的「用途/更新日期」開頭註解區塊與逐行註解,於階段 C 由 `/jsc-doc:funcs` 的指令檔流程統一補齊/覆寫為標準格式;本階段先確保**建置行為**正確、六步結構清楚即可(階段 C funcs 第 11 步亦會做語法驗證)。
```Dockerfile
# syntax=docker/dockerfile:1
# 1. 參數處理:可調參數集中於檔首,以 ARG 注入
ARG BUILD_IMAGE=<build-base>
ARG RUNTIME_IMAGE=<runtime-base-slim>
# ---- build 階段:安裝相依、複製原始碼、建置產物 ----
FROM ${BUILD_IMAGE} AS build
WORKDIR /app
# 2. 安裝套件:先帶相依描述以利 layer 快取,再安裝(並清理快取縮小該層)
COPY <dependency-manifests> ./
RUN <install-deps>
# 3. 複製檔案:複製其餘原始碼(搭配 .dockerignore)
COPY . .
# 4. 執行程序:build / compile / transpile、必要權限設定
RUN <build-command>
# ---- 5. 縮小映像檔:runtime 改用較小基底,只帶執行所需產物 ----
FROM ${RUNTIME_IMAGE} AS runtime
WORKDIR /app
COPY --from=build /app/<artifacts> ./<artifacts>
# (搬移 runtime 需要的 ENV / EXPOSE / USER,語意與原檔一致)
# 6. 設定入口:ENTRYPOINT / CMD 置於最後,語意同原檔
ENTRYPOINT [<entrypoint>]
```
- **自我檢查**:依 `/jsc-shared:spec-dockerfile` 的自我檢查清單(跨階段 `ARG` 重新宣告、runtime 帶齊產物、對外契約一致、路徑一致)。
- 此檔的「用途/更新日期」開頭註解區塊與逐行註解,於階段 C 由 `/jsc-doc:funcs` 的指令檔流程統一補齊/覆寫為標準格式;本階段先確保**建置行為**正確、六步結構清楚即可。
- **建置驗證**:若環境可執行 `docker build`,重整後做一次建置驗證(或至少 `docker build --check`/語法檢查)確認可建置;無法執行時明確說明原因並標註風險(階段 C funcs 第 11 步亦會做語法驗證)。
--- ---
## 階段 C:完整執行 /jsc-doc:funcs 處理流程 ## 階段 C:完整執行 /jsc-doc:funcs 處理流程
Dockerfile 六步整理完成後,以階段 A1 的專案根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個專案**完整執行 `/jsc-doc:funcs` 流程(前置可用性檢查、完整流程、由使用者裁示實作方式、重建 README;本次整理的 `Dockerfile` 會被視為部署設定檔補齊標頭與逐行註解)。 Dockerfile 六步整理完成後,以階段 A1 的專案根目錄為目標,依 `/jsc-shared:spec-doc-funcs-handoff` 對**整個專案**完整執行 `/jsc-doc:funcs`。
--- ---
@@ -145,10 +104,4 @@ Dockerfile 六步整理完成後,以階段 A1 的專案根目錄為目標,
## 呼叫方式 ## 呼叫方式
格式:`[--project-dir <專案根目錄>] [--dockerfile <Dockerfile 路徑>] [--yes]` — 全部可省略(專案根目錄預設目前工作目錄;Dockerfile 自動定位,多個時詢問)。 依 `/jsc-shared:spec-skill-invocation` 執行,以 `image` 作為本 skill 名稱代入其呼叫方式表格;參數格式見上方〔參數〕章節。
| 助理 | 呼叫 |
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:image`,或 `/jsc-code:image --project-dir ~/work/my-app --dockerfile build/Dockerfile` |
| Codex | `$image`,或 `$image --dockerfile docker/Dockerfile`,或用 `/skills` 選單 |
| OpenCode | 描述需求(如「把這個專案的 Dockerfile 整理成參數處理→安裝套件→複製檔案→執行程序→縮小映像檔→設定入口六步、用多階段建置縮小映像,最後跑 funcs 補文件並重建 README」)自動觸發 |
+22 -43
View File
@@ -18,21 +18,15 @@ argument-hint: "[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以
--- ---
## 絕對準則(不可違反) ## 共用規範(必要前置)
- **不建立任何草稿檔**:不寫 `.docs/`、不寫暫存檔、不用本機檔案傳遞中間結果。需求彙整、TODO 排序、實作進度、驗證結果等所有中間成果,一律留在**對話內容**,並透過 `tea` 或 Gitea API **保存到議題描述或議題留言**。唯一例外是階段 E 在議題範圍內對**目標 repo 原始碼**的正常程式修改。 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
- **盡量使用表格或圖形**:依 `/jsc-shared:spec-output` — 面向使用者的輸出與寫入議題的內容(需求彙整、TODO 列表、進度回報),優先以 Markdown 表格與 Mermaid 圖呈現,忠實反映議題內容、不得杜撰。 依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`、`spec-no-scratch-files`、`spec-issue-read`、`spec-todo-list`、`spec-skill-invocation`、`spec-model`
## 共用規範(shared plugin,必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行:
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼、API body 以 UTF-8 JSON 檔帶入且換行為實際換行。
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認、已知資訊跳過詢問。
- `/jsc-shared:spec-gitea`:tea/API 工具選擇與檢查、`GITEA_TOKEN` 機密保護(不 echo、遮蔽)、不依賴 `jq`、API 分頁完整讀取。
本 skill 特有補充: 本 skill 特有補充:
- 階段 E 在議題範圍內對**目標 repo 原始碼**的正常程式修改,是 `spec-no-scratch-files` 不落地準則的**唯一例外**(不算草稿檔)。
- **必要決策**(會中斷詢問):工具皆不可用、專案不明、議題編號缺失、TODO 與需求衝突需人工裁示、實作失敗需使用者決策。 - **必要決策**(會中斷詢問):工具皆不可用、專案不明、議題編號缺失、TODO 與需求衝突需人工裁示、實作失敗需使用者決策。
- 階段 A/B/C 的詢問在「可跳過條件」成立時**必須跳過**,不要重複確認已知資訊。 - 階段 A/B/C 的詢問在「可跳過條件」成立時**必須跳過**,不要重複確認已知資訊。
@@ -54,7 +48,7 @@ argument-hint: "[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以
**若使用者已透過 `--tool` 或對話明確選定工具,跳過詢問**,只做該工具的可用性驗證。 **若使用者已透過 `--tool` 或對話明確選定工具,跳過詢問**,只做該工具的可用性驗證。
依 `/jsc-shared:spec-gitea` 的工具選擇流程執行:檢查 `tea`(`command -v tea`、`tea login list`)與 `GITEA_TOKEN`(只輸出「已設定/未設定」)→ 以表格呈現檢查結果後詢問使用者要用 `tea` 或 `api` → 兩種方式都不可用則回報並停止(不要請使用者把 token 貼進對話)。 依 `/jsc-shared:spec-gitea` 的工具選擇流程執行。
## 階段 B:確認議題所在專案(已知則跳過) ## 階段 B:確認議題所在專案(已知則跳過)
@@ -81,49 +75,34 @@ argument-hint: "[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以
對每一個選定議題執行: 對每一個選定議題執行:
### D1. 讀取議題完整內容 ### D1. 讀取議題完整內容並彙整需求
- 讀取 `title`、`body`、`state`、`labels`、`milestone`、`assignees` 與**所有留言**: 依 `/jsc-shared:spec-issue-read` 讀取議題完整內容(描述、所有留言、所有附件)並彙整需求;`title`/`state`/`labels`/`milestone`/`assignees` 一併取得供階段 B/C 與後續留言引用。
- `tea`:`tea issues <index> --repo <owner>/<repo> --comments`
- `api`:`GET {base}/issues/{index}` 與 `GET {base}/issues/{index}/comments`
- 留言中若有需求補充、變更或取消,必須納入需求彙整,並以最新留言為準。
### D2. 彙整需求並盤點既有 TODO ### D2. 盤點既有 TODO 並產生排序後的 TODO 列表
- 把議題描述與留言整理成**需求彙整**:目標、驗收條件、限制條件;只做歸納,不編造議題未提及的需求,不確定處標註「需人工確認」。 依 `/jsc-shared:spec-todo-list` 盤點議題描述既有 TODO、產生新 TODO(格式、可驗收性、禁止編造、依影響範圍小→大排序、舉證 `path:line` 或需求語句)。
- 盤點議題描述中既有的 Markdown checklist(`- [ ]` / `- [x]`)作為既有 TODO;已勾選項目視為已完成,不重做。
### D3. 產生排序後的 TODO 列表 - **勾稽需求覆蓋度**:逐條比對 D1 需求彙整與 TODO 列表;若既有 TODO **不足以達成議題描述與需求**,補上缺漏的 TODO(標註「新增」)。
- 每項 TODO 必須**可執行、可驗收**,並評估其**影響範圍**(預計修改的檔案/模組數與波及面):
| 影響範圍 | 定義(參考) |
| --- | --- |
| XS | 單一檔案內的局部修改(文案、設定值、小修正) |
| S | 單一檔案或單一函式的邏輯調整 |
| M | 同一模組內跨多檔案的修改 |
| L | 跨模組修改或介面/契約變更 |
| XL | 跨專案、資料結構或流程性的大改動 |
- **依影響範圍由小到大排序**(XS → XL);範圍相同時,前置依賴在前。
- **勾稽需求覆蓋度**:逐條比對需求彙整與 TODO 列表;若既有 TODO **不足以達成議題描述與需求**,補上缺漏的 TODO(標註「新增」)。
- 有助理解時,在留言或描述中加入 Mermaid 流程圖呈現 TODO 之間的依賴與執行順序。 - 有助理解時,在留言或描述中加入 Mermaid 流程圖呈現 TODO 之間的依賴與執行順序。
### D4. 缺漏 TODO 附加到議題描述 ### D3. 缺漏 TODO 附加到議題描述
- 若 D3 有新增 TODO,用 `tea` 或 API **更新議題描述**:保留原描述內容,於既有 `## TODO` 區塊補上新項目;沒有該區塊時在描述最後加上 `## TODO`: - 若 D2 有新增 TODO,依 `/jsc-shared:spec-todo-list` 的區塊格式規則,用 `tea` 或 API **更新議題描述**:
- `tea`:`tea issues edit <index> --repo <owner>/<repo> --description <更新後全文>`(tea 版本不支援時改用 API) - `tea`:`tea issues edit <index> --repo <owner>/<repo> --description <更新後全文>`(tea 版本不支援時改用 API)
- `api`:`PATCH {base}/issues/{index}`,body 以 UTF-8 JSON 檔帶入 `{"body": "..."}` - `api`:`PATCH {base}/issues/{index}`,body 以 UTF-8 JSON 檔帶入 `{"body": "..."}`
- 更新前先向使用者以表格摘要「新增了哪些 TODO、為什麼需要」;帶 `--yes` 時直接執行並回報。 - 更新前先向使用者以表格摘要「新增了哪些 TODO、為什麼需要」;帶 `--yes` 時直接執行並回報。
- 沒有新增 TODO 時不改動議題描述。 - 沒有新增 TODO 時不改動議題描述。
若議題描述或其中的 TODO 清單帶有等價於 `model:` frontmatter 的模型宣告(或本 skill 未來擴充為讀取帶 `model:` frontmatter 的檔案),依 `/jsc-shared:spec-model` 的『讀到帶 `model:` frontmatter 的清單檔時的檢查義務』先做模型檢查,再進入階段 E。
## 階段 E:逐項實作 TODO,每完成一項留言到議題 ## 階段 E:逐項實作 TODO,每完成一項留言到議題
依 D3 排序(多議題時先完成一個議題的所有 TODO,再進入下一個議題;有跨議題依賴時先處理被依賴者)逐項執行: 依 D2 排序(多議題時先完成一個議題的所有 TODO,再進入下一個議題;有跨議題依賴時先處理被依賴者)逐項執行:
1. **實作**:在本機 repo 依 TODO 內容實作,只修改該 TODO 必要範圍;發現需要擴大範圍或牽動其他 TODO 時,先停止並詢問使用者。 1. **實作**:在本機 repo 依 TODO 內容實作,只修改該 TODO 必要範圍;發現需要擴大範圍或牽動其他 TODO 時,先停止並詢問使用者。
2. **驗證**:執行適合專案的建置/測試/驗證;失敗時修正到通過,無法通過則記錄原因並在留言中標註「待人工處理」。 2. **驗證**:執行適合專案的建置/測試/驗證;失敗時修正到通過,無法通過則記錄原因並在留言中標註「待人工處理」。
3. **更新議題描述**:把該 TODO 的 checkbox 由 `- [ ]` 勾成 `- [x]`(同 D4 的更新方式)。 3. **更新議題描述**:把該 TODO 的 checkbox 由 `- [ ]` 勾成 `- [x]`(同 D3 的更新方式);勾選與留言回報依 `/jsc-shared:spec-todo-list` 的完成回報規則。
4. **留言進度到議題**(每完成一項就留言,不可累積到最後一次補): 4. **留言進度到議題**(每完成一項就留言,不可累積到最後一次補):
- `tea`:`tea comment <index> --repo <owner>/<repo> <內容>` - `tea`:`tea comment <index> --repo <owner>/<repo> <內容>`
- `api`:`POST {base}/issues/{index}/comments`,body 以 UTF-8 JSON 檔帶入 - `api`:`POST {base}/issues/{index}/comments`,body 以 UTF-8 JSON 檔帶入
@@ -150,8 +129,8 @@ argument-hint: "[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以
格式:`[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以逗號分隔>] [--host <gitea 主機>] [--yes]` — 全部可省略;省略時依階段 A/B/C 詢問(已知資訊一律跳過詢問)。token 一律由環境變數 `GITEA_TOKEN` 提供。 格式:`[--tool <tea|api>] [--repo <owner/repo>] [--issues <編號,以逗號分隔>] [--host <gitea 主機>] [--yes]` — 全部可省略;省略時依階段 A/B/C 詢問(已知資訊一律跳過詢問)。token 一律由環境變數 `GITEA_TOKEN` 提供。
| 助理 | 呼叫 | 各助理呼叫方式依 `/jsc-shared:spec-skill-invocation`(本 skill 為 `/jsc-code:issues`),常用範例:
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:issues`,或 `/jsc-code:issues --tool api --repo plugins/code --issues 12,15 --yes` | - Claude Code / Antigravity:`/jsc-code:issues`,或 `/jsc-code:issues --tool api --repo plugins/code --issues 12,15 --yes`
| Codex | `$issues`,或 `$issues --tool tea --repo plugins/code --issues 12`,或用 `/skills` 選單 | - Codex:`$issues`,或 `$issues --tool tea --repo plugins/code --issues 12`,或用 `/skills` 選單
| OpenCode | 描述需求(如「用 GITEA_TOKEN 讀 plugins/code 的 12、15 號議題,整理需求成 TODO 依影響範圍小到大排序、缺的補進議題描述,然後逐項實作、每完成一項留言進度」)自動觸發 | - OpenCode:描述需求(如「用 GITEA_TOKEN 讀 plugins/code 的 12、15 號議題,整理需求成 TODO 依影響範圍小到大排序、缺的補進議題描述,然後逐項實作、每完成一項留言進度」)自動觸發
+6 -11
View File
@@ -8,12 +8,11 @@ argument-hint: "[<solution-or-project>] [--include-prerelease] [--no-dedupe] [--
把 C# / .NET repo 內的 NuGet 套件依專案盤點、清理可安全移除的重複參考,然後逐一更新到最新可用版本。每個套件異動後都要驗證專案可以正常編譯;若最新版本失敗,改從目前版本之後的最小可用版本逐版升級,直到第一個編譯失敗版本為止,保留最後一個可編譯版本並記錄失敗點。 把 C# / .NET repo 內的 NuGet 套件依專案盤點、清理可安全移除的重複參考,然後逐一更新到最新可用版本。每個套件異動後都要驗證專案可以正常編譯;若最新版本失敗,改從目前版本之後的最小可用版本逐版升級,直到第一個編譯失敗版本為止,保留最後一個可編譯版本並記錄失敗點。
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼(套件 id、檔名、指令、版本號保留原文)。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
## 輸出規範(本 skill 特有) ## 輸出規範(本 skill 特有)
@@ -133,7 +132,7 @@ argument-hint: "[<solution-or-project>] [--include-prerelease] [--no-dedupe] [--
### 5. 特殊檔案與版本管理 ### 5. 特殊檔案與版本管理
- `packages.lock.json` 存在時,更新套件後允許 lock file 跟著變更;回復失敗更新時也要回復 lock file。 - `packages.lock.json` 存在時,更新套件後允許 lock file 跟著變更;回復失敗更新時也要回復 lock file。
- `nuget.config` 有私有 feed 時,不輸出 credential;錯誤訊息若含 token / password 必須遮蔽。 - `nuget.config` 有私有 feed 時,不輸出 credential;token / password 的機密保護依 `/jsc-shared:spec-gitea` 執行。
- `packages.config` 專案不套用 `dotnet add package` 流程;先回報這是舊格式,使用 `nuget.exe update` 或請使用者確認遷移策略。 - `packages.config` 專案不套用 `dotnet add package` 流程;先回報這是舊格式,使用 `nuget.exe update` 或請使用者確認遷移策略。
- 多 target framework 專案以完整目標 build 為準,不只 build 單一 framework。 - 多 target framework 專案以完整目標 build 為準,不只 build 單一 framework。
@@ -155,8 +154,4 @@ argument-hint: "[<solution-or-project>] [--include-prerelease] [--no-dedupe] [--
## 呼叫方式 ## 呼叫方式
| 助理 | 呼叫 | 依 `/jsc-shared:spec-skill-invocation` 執行,以 `nuget` 作為本 skill 名稱代入其呼叫方式表格。
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:nuget`,或 `/jsc-code:nuget MySolution.sln --include-prerelease` |
| Codex | `$nuget`,或 `$nuget src/App/App.csproj --build "dotnet build App.sln -c Release"` |
| OpenCode | 描述需求(如「把這個 .NET solution 的 NuGet 都更新到最新,先清掉 ProjectReference 已提供的重複套件,每更新一包就 build」)自動觸發 |
+30 -166
View File
@@ -18,14 +18,11 @@ argument-hint: "[--issue <編號|編號清單|all>] [--findings <findings 檔或
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼(含 commit 訊息、PR 標題/描述、寫檔/API body 實作要點與送出前自我檢查)。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`、`spec-git-safety`、`spec-issue-read`、`spec-conventional-commit`、`spec-git-push`、`spec-pull-request`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
- `/jsc-shared:spec-gitea`:`GITEA_TOKEN` 機密保護(不 echo、遮蔽 `***`)、API body 以 UTF-8 JSON 檔帶入。
- `/jsc-shared:spec-git-safety`:不破壞既有工作、develop → master 後備、保守解衝突、新分支不覆蓋既有分支。
本 skill 特有補充: 本 skill 特有補充:
@@ -82,21 +79,7 @@ git rev-parse --verify --quiet "origin/${current_branch}"
``` ```
- **`origin/<current_branch>` 存在** → 維持當前分支,直接進入 A4 更新到最新;不要只因目前分支有遠端同名分支就建立新工作分支,後續只有來源分支與 PR 目標分支相同時才需要開新分支。 - **`origin/<current_branch>` 存在** → 維持當前分支,直接進入 A4 更新到最新;不要只因目前分支有遠端同名分支就建立新工作分支,後續只有來源分支與 PR 目標分支相同時才需要開新分支。
- **`origin/<current_branch>` 不存在**(當前分支為本地獨有,遠端無對應)→ 依序嘗試切換到後備分支: - **`origin/<current_branch>` 不存在**(當前分支為本地獨有,遠端無對應)→ 切換前先確認工作區可安全切換(承接 A1 結果,若有未提交變更導致切換失敗,停止並回報,請使用者先處理未提交變更;不可強制丟棄),再依 `/jsc-shared:spec-git-safety`「develop → master 後備分支」切換到後備分支;develop/master 在遠端都不存在時,依該規範回報並停止,不臆測其他分支。
1. 切換前先確認工作區可安全切換(承接 A1 結果)。若有未提交變更導致切換失敗,停止並回報,請使用者先處理未提交變更;不可強制丟棄。
2. 若 `origin/develop` 存在 → 切換到 `develop`:
```bash
git switch develop 2>/dev/null || git switch -c develop --track origin/develop
```
3. 否則若 `origin/master` 存在 → 切換到 `master`:
```bash
git switch master 2>/dev/null || git switch -c master --track origin/master
```
4. **`develop` 與 `master` 在遠端都不存在** → 回報「當前分支不在遠端,且找不到 develop/master 後備分支」並停止,不臆測其他分支。
- 切換完成後,A4 先更新該後備分支到最新;是否建立新的工作分支交由 A5 依來源分支與 PR 目標分支是否相同判斷。切換到後備分支屬不可忽略的狀態變更,需在輸出中明確告知使用者已從原分支切換到哪一個分支。 - 切換完成後,A4 先更新該後備分支到最新;是否建立新的工作分支交由 A5 依來源分支與 PR 目標分支是否相同判斷。切換到後備分支屬不可忽略的狀態變更,需在輸出中明確告知使用者已從原分支切換到哪一個分支。
### A4. Pull(更新到最新) ### A4. Pull(更新到最新)
@@ -121,16 +104,13 @@ source_branch="$(git rev-parse --abbrev-ref HEAD)"
``` ```
- **若後續會建立 PR(未帶 `--no-pr`)且尚未知道目標分支** → 先依階段 E1 的規則詢問目標分支,避免後續修復 commit 落在與 PR 目標同名的來源分支上才發現 source/base 相同。若帶 `--no-pr`,此階段不因缺少目標分支而詢問。 - **若後續會建立 PR(未帶 `--no-pr`)且尚未知道目標分支** → 先依階段 E1 的規則詢問目標分支,避免後續修復 commit 落在與 PR 目標同名的來源分支上才發現 source/base 相同。若帶 `--no-pr`,此階段不因缺少目標分支而詢問。
- **若來源分支名稱與目標分支相同** → 不在該分支上直接修復/commit,也不可後續直接建立 head=base 的 PR。從目前已更新到最新的目標分支建立新的工作分支,後續階段(修復、commit、push、PR 的 `head`)都以新分支為準。 - 是否需要建立新的工作分支、判斷方式(只有同名才開新分支)與命名避免覆蓋既有分支,依 `/jsc-shared:spec-git-safety`「工作分支選擇」與「建立分支不覆蓋」執行;本 skill 新工作分支預設命名:
- **若來源分支名稱與目標分支不同** → 維持目前分支,不另開分支;即使目前分支存在 `origin/<source_branch>`,也照常在目前分支處理。
- 新分支名稱需可讀且避免覆蓋既有分支;預設格式:
```bash ```bash
work_branch="ai-review-resolve/${source_branch}-$(date +%Y%m%d-%H%M%S)" work_branch="ai-review-resolve/${source_branch}-$(date +%Y%m%d-%H%M%S)"
git switch -c "${work_branch}" git switch -c "${work_branch}"
``` ```
- 建立前若本地或遠端已存在同名分支,換一個時間戳或短 hash,不可覆蓋既有分支。
- 若因未提交變更導致建立/切換新分支失敗,停止並回報,請使用者先處理未提交變更;不可強制丟棄。 - 若因未提交變更導致建立/切換新分支失敗,停止並回報,請使用者先處理未提交變更;不可強制丟棄。
- 建立新分支屬不可忽略的狀態變更,需在輸出中明確告知使用者「因來源分支與目標分支同名,已從 `<source_branch>` 建立並切換到 `<work_branch>`」。 - 建立新分支屬不可忽略的狀態變更,需在輸出中明確告知使用者「因來源分支與目標分支同名,已從 `<source_branch>` 建立並切換到 `<work_branch>`」。
@@ -216,15 +196,7 @@ source_branch="$(git rev-parse --abbrev-ref HEAD)"
1. 解析 repo 座標(同 E3),token 由 `GITEA_TOKEN` 提供。 1. 解析 repo 座標(同 E3),token 由 `GITEA_TOKEN` 提供。
2. `all` 時先取所有 open 議題(分頁全取):`GET /repos/<owner>/<repo>/issues?state=open&type=issues`。 2. `all` 時先取所有 open 議題(分頁全取):`GET /repos/<owner>/<repo>/issues?state=open&type=issues`。
3. 對每個選定議題讀本文與全部留言(分頁全取;輸出遮蔽 token): 3. 對每個選定議題讀取本文、全部留言與全部附件,依 `/jsc-shared:spec-issue-read` 執行(分頁完整讀取、不得只取部分留言;輸出遮蔽 token);議題標題/描述與附件內容作為整體背景脈絡納入判斷,本階段只從留言中解析下列嚴重問題留言格式。
```bash
curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"https://<host>/api/v1/repos/<owner>/<repo>/issues/<編號>"
curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"https://<host>/api/v1/repos/<owner>/<repo>/issues/<編號>/comments"
```
4. 解析每則符合「嚴重問題留言」格式(action `severeCommentBody` 產出)的留言為一條 finding: 4. 解析每則符合「嚴重問題留言」格式(action `severeCommentBody` 產出)的留言為一條 finding:
``` ```
@@ -238,7 +210,7 @@ source_branch="$(git rev-parse --abbrev-ref HEAD)"
<suggestedCode> <suggestedCode>
``` ```
映射為 `{ reviewer, severity, file, startLine, endLine, problem, suggestion, suggestedCode }`,並記住**來源議題編號與留言 id**(供 B6 回寫);議題標題/描述作為整體背景脈絡納入判斷。 映射為 `{ reviewer, severity, file, startLine, endLine, problem, suggestion, suggestedCode }`,並記住**來源議題編號與留言 id**(供 B6 回寫)。
5. 某議題解析不到任何嚴重問題留言 → 記錄並略過該議題(`all`/多選時不因單一議題無問題而中斷整批),不臆測。 5. 某議題解析不到任何嚴重問題留言 → 記錄並略過該議題(`all`/多選時不因單一議題無問題而中斷整批),不臆測。
**B1c. 合併去重** **B1c. 合併去重**
@@ -325,76 +297,19 @@ source_branch="$(git rev-parse --abbrev-ref HEAD)"
## 階段 C:分析變更並分類提交(`--no-commit` 時略過) ## 階段 C:分析變更並分類提交(`--no-commit` 時略過)
### C1. 盤點工作區變更 依 `/jsc-shared:spec-conventional-commit` 完整盤點工作區變更(含所有已暫存/未暫存/未追蹤/改名/刪除檔案,一律以 `git status --porcelain=v1 -uall` 為主,`git diff` 只作輔助核對)、依實際異動內容歸類為 9 種 commit type、產出提交計畫,再逐組精準 `git add -- <路徑>`(不用 `git add -A`/`git add .`)後分別 `git commit -m "type(範圍): 一句總結"`。
```bash 本 skill 特有補充:
git status --porcelain=v1 -uall
git diff # 已追蹤檔的未暫存變更
git diff --staged # 已暫存變更
git ls-files --others --exclude-standard # 未追蹤檔
```
盤點時必須以 `git status --porcelain=v1 -uall` 為主,不可只看 `git diff`,因為那會漏掉未追蹤檔。盤點範圍必須包含**所有**變更:已修改檔、新增檔(`??` 未追蹤檔)、刪除檔、改名檔,以及已暫存與未暫存變更。`git diff`、`git diff --staged`、`git ls-files --others --exclude-standard` 只作為輔助核對。**無任何變更** → 回報「工作區無變更可提交」,跳過提交直接進入階段 D(D 會因無 commit 可 push 而跳到階段 E)。 - **階段 B 產生的變更歸類**:修 bug→`fix`、補功能→`feat`、改文件→`docs`…;`.gitea/ai-review/findings/` 目錄下 `*.json`(wrapper)與 `exclusions.json` 的問題狀態更新一律歸 `chore`。
- **無任何變更** → 回報「工作區無變更可提交」,跳過提交直接進入階段 D(D 會因無 commit 可 push 而跳到階段 E)。
### C2. 依異動內容歸類 conventional commit 類型
逐一檢視每個變更檔的**實際異動內容**(不只看路徑),歸入下列其一;**所有 `??` 未追蹤檔都必須納入分類**,不能因為它們不在 `git diff` 裡就漏掉:
| type | 適用情境 |
| --- | --- |
| `feat` | 新增功能/新行為/新 API/新 skill |
| `fix` | 修正錯誤、修掉 bug(**階段 B 的 bug 修復多半歸此**) |
| `docs` | 只改文件(README、註解、`*.md`、說明) |
| `style` | 不影響邏輯的格式調整(排版、空白、分號、命名一致化) |
| `refactor` | 重構:不改外部行為的內部結構調整 |
| `perf` | 效能優化 |
| `test` | 新增或修改測試 |
| `chore` | 雜項:建置、設定、相依套件、版本號 bump、忽略檔等 |
| `revert` | 還原先前的提交 |
- **同一檔案橫跨多型** → 以該檔**主要異動性質**歸類;難以拆分時就近歸入影響最大的一類,並在總結註記。
- **階段 B 修復產生的變更**:依其性質歸類(修 bug→`fix`、補功能→`feat`、改文件→`docs`…)。`.gitea/ai-review/findings/` 目錄下 `*.json`(wrapper)與 `exclusions.json` 的問題狀態更新歸 `chore`。
- **未追蹤新檔**:必須照實際內容歸入對應 type,必要時在提交前明確 `git add -- <path>`,不可因為是新檔就略過。
### C3. 產出提交計畫
把變更檔依 type 分組,**每個 type 一個 commit**,輸出提交計畫;**所有變更項目都必須被追蹤並納入計畫,不能遺漏任何 `??` 未追蹤檔**。除非使用者要求確認或分組有不可忽略的取捨,否則直接提交:
| 順序 | type(範圍) | commit 訊息 | 納入檔案 |
| --- | --- | --- | --- |
- **commit 訊息格式**:`type(範圍): 一句總結` —
- `type`:上表英文類型。
- `範圍`(括號內):**必須是這組異動實際牽涉的功能/模組/元件名稱**,而**不是**重述 type 的類別詞。
取名規則:優先沿用程式碼/專案中既有的識別名(檔名、模組名、skill 名、功能名,可中可英、保持與原碼一致),讓人一眼看出「改到哪個東西」。
- ✅ 對:`feat(使用者登入)`、`fix(結帳流程)`、`perf(物件查詢)`、`docs(README)`、`refactor(訂單服務)`、`chore(plugin 版本)`、`feat(review-resolve)`。
- ❌ 錯(只是重述 type,禁止):`feat(新增功能)`、`fix(修正錯誤)`、`perf(優化效能)`、`docs(文件)`、`chore(雜項)`。
- 一組異動橫跨多個功能而無單一主體時,才退而取最貼近的上層範圍(例如多個 manifest → `plugin 設定`)。
- `一句總結`:把這個 commit 內所有異動**總結成一句**繁體中文(簡短、聚焦做了什麼)。
- 範例:`feat(使用者登入): 新增帳密登入與 token 簽發`、`fix(結帳流程): 修正空購物車導致的結帳例外`、`perf(物件查詢): 改用批次查詢降低 DB 往返`、`docs(README): 補上安裝與呼叫方式說明`、`chore(ai-review 狀態): 更新 findings 與 exclusions.json`。
- **提交順序建議**:`fix`/`feat` 等核心異動在前,`docs`/`style`/`chore` 在後(純屬建議,可依相依性調整)。
- **盤點要求**:提交計畫必須完整對應 C1 盤點出的所有變更項目,若有新檔或未追蹤檔,必須明確列入對應 commit,不能只根據 `git diff` 下結論。
### C4. 執行分類提交
對每組依序:
```bash
git add -- <該組檔案...> # 僅暫存該組檔案,逐組精準 add
git commit -m "type(範圍): 一句總結" # 範圍=實際異動的功能/模組名
```
- **逐組 add/commit**,確保每個 commit 只含該類異動;不要一次 `git add -A` 再混在一起。
- 改名/刪除檔一併納入對應組的 `git add`(`git add -A -- <路徑>` 或明確列出)。
- **所有 `??` 未追蹤檔都必須納入提交**;若是新檔,必要時要明確執行 `git add -- <path>`,不可漏掉。
- **不可只根據 `git diff` 判斷變更**;C1 盤點出的所有異動都必須反映到分類與提交,否則視為提交計畫不完整。
- commit 完成後進入階段 D(push)。`--no-commit` 時不進入後續階段;若工作區無變更而沒有產生任何新 commit,仍進入階段 D,由 D 判斷無 commit 可 push 後跳到階段 E。 - commit 完成後進入階段 D(push)。`--no-commit` 時不進入後續階段;若工作區無變更而沒有產生任何新 commit,仍進入階段 D,由 D 判斷無 commit 可 push 後跳到階段 E。
--- ---
## 階段 D:Push 當前分支(`--no-commit` 時略過;無 commit 可 push 時跳到階段 E) ## 階段 D:Push 當前分支(`--no-commit` 時略過;無 commit 可 push 時跳到階段 E)
commit 完成後推送**當前分支**,依序嘗試三種方式,前者失敗才退到下一個: commit 完成後推送**當前分支**,憑證選擇的三段式流程(認證管理器 → token push → 詢問使用者,前者失敗才退到下一個;token 全程遮蔽、用完即棄不落地)依 `/jsc-shared:spec-git-push` 執行。
推送前先記錄目前來源分支與其遠端基準,並確認是否有 commit 需要 push: 推送前先記錄目前來源分支與其遠端基準,並確認是否有 commit 需要 push:
@@ -409,24 +324,6 @@ git log --oneline "origin/${source_branch}..${source_branch}" # 領先遠端
- 若後續會建立 PR(未帶 `--no-pr`)且尚未知道目標分支,先依階段 E1 的規則詢問目標分支,這屬於不可忽略的必要決策。 - 若後續會建立 PR(未帶 `--no-pr`)且尚未知道目標分支,先依階段 E1 的規則詢問目標分支,這屬於不可忽略的必要決策。
- 若來源分支名稱與目標分支相同,**不要 push 原來源分支**;記下來源分支與遠端基準,直接進入階段 E,由 E2 建立新的 PR 來源分支、帶入 commit 後再 push 新分支。 - 若來源分支名稱與目標分支相同,**不要 push 原來源分支**;記下來源分支與遠端基準,直接進入階段 E,由 E2 建立新的 PR 來源分支、帶入 commit 後再 push 新分支。
1. **認證管理器(優先)**:直接用 git 既有的 credential helper(如 Windows 的 `manager-core`):
```bash
git push -u origin "$(git rev-parse --abbrev-ref HEAD)"
```
2. **失敗 → 改用 token push**:從環境變數讀 token,組帶 token 的遠端 URL 推送。**整個過程不可把含 token 的指令/URL 印出來**(用變數帶入、輸出時遮蔽):
```bash
# GITEA_TOKEN 來自環境變數;解析 origin 的 host/owner/repo
git push "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" \
"$(git rev-parse --abbrev-ref HEAD)"
```
(token 用完即棄,不寫進 git remote 設定、不落地。)
3. **再失敗 → 詢問使用者要如何 push**:列出失敗原因(遮蔽 token),請使用者指示推送方式,**不可自行猜測**其他憑證或來源。
push 成功後記下遠端分支名,進入階段 E。若因來源分支與目標分支相同而延後 push,記下原因並進入階段 E2 建立新分支。 push 成功後記下遠端分支名,進入階段 E。若因來源分支與目標分支相同而延後 push,記下原因並進入階段 E2 建立新分支。
--- ---
@@ -437,9 +334,9 @@ push 成功後記下遠端分支名,進入階段 E。若因來源分支與目
- 帶 `--target <分支>` → 直接採用。 - 帶 `--target <分支>` → 直接採用。
- 若階段 D 已為了避免 source/base 相同而詢問過目標分支,沿用該目標分支。 - 若階段 D 已為了避免 source/base 相同而詢問過目標分支,沿用該目標分支。
- **否則必須詢問使用者目標分支**(可用 `git branch -r` 列出輔助選擇),**嚴禁臆測或預設**(不可自行假設 develop/main/master)。 - **否則**依 `/jsc-shared:spec-pull-request`「目標分支不得臆測」詢問使用者(可用 `git branch -r` 列出輔助選擇),嚴禁臆測或預設。
### E2. 若來源分支與目標分支相同,改建 PR 來源分支 ### E2. 若來源分支與目標分支相同,改建 PR 來源分支(cherry-pick 帶入已提交的異動)
先取得目前 PR 來源分支: 先取得目前 PR 來源分支:
@@ -447,7 +344,7 @@ push 成功後記下遠端分支名,進入階段 E。若因來源分支與目
git rev-parse --abbrev-ref HEAD git rev-parse --abbrev-ref HEAD
``` ```
若來源分支名稱與 `--target` 指定(或使用者選定)的目標分支相同,**不可直接建立 head=base 的 PR,也不可先把原來源分支 push 到目標分支**。改用下列流程建立新的 PR 來源分支,並把原來源分支的 commit 帶入後再開 PR: 若來源分支名稱與 `--target` 指定(或使用者選定)的目標分支相同,依 `/jsc-shared:spec-git-safety`「工作分支選擇」,**不可直接建立 head=base 的 PR,也不可先把原來源分支 push 到目標分支**。此處的情境是修復與 commit 已經發生在這個(事後才發現同名的)來源分支上,因此改用下列流程建立新的 PR 來源分支,把原來源分支的 commit cherry-pick 帶入後再開 PR:
1. 先記錄原來源分支名稱與要帶入的 commit 清單: 1. 先記錄原來源分支名稱與要帶入的 commit 清單:
@@ -460,7 +357,7 @@ git rev-parse --abbrev-ref HEAD
- 若沒有任何 commit 可帶入,回報「來源分支沒有領先目標分支的 commit」,停止開 PR。 - 若沒有任何 commit 可帶入,回報「來源分支沒有領先目標分支的 commit」,停止開 PR。
- 若階段 D 已記錄來源分支的遠端基準,使用該基準判斷要帶入的 commit,避免把原來源分支直接推進目標分支。 - 若階段 D 已記錄來源分支的遠端基準,使用該基準判斷要帶入的 commit,避免把原來源分支直接推進目標分支。
2. 從目標分支的遠端基準建立新分支。新分支名稱需可讀且避免覆蓋既有分支,例如: 2. 從目標分支的遠端基準建立新分支,命名依 `/jsc-shared:spec-git-safety`「建立分支不覆蓋」避免覆蓋既有分支,例如:
```bash ```bash
git switch -c "ai-review-resolve/<短時間戳>" "origin/${target}" git switch -c "ai-review-resolve/<短時間戳>" "origin/${target}"
@@ -472,60 +369,27 @@ git rev-parse --abbrev-ref HEAD
git cherry-pick "origin/${target}..${source_branch}" git cherry-pick "origin/${target}..${source_branch}"
``` ```
- cherry-pick 發生衝突時,先告知使用者,再依專案脈絡嘗試最小合理解衝突。 衝突時依 `/jsc-shared:spec-git-safety`「保守解衝突」處理(先告知使用者、依專案脈絡嘗試最小合理整合;可安全解決者移除衝突標記、`git add -- <檔案...>` 後 `git cherry-pick --continue`;無法安全判斷者停止並列出決策點,不硬選任一邊)。
- 可安全解決的衝突:移除衝突標記、`git add -- <檔案...>`,再繼續 `git cherry-pick --continue`。
- 無法安全判斷的衝突:停止處理,列出衝突檔案與需要使用者決策的點;不要硬選任一邊。
4. 將新分支 push 到遠端,使用階段 D 的同一套 push 憑證策略,並把後續 PR 的 `head` 改為這個新分支。 4. 將新分支 push 到遠端,憑證策略依 `/jsc-shared:spec-git-push`(同階段 D),並把後續 PR 的 `head` 改為這個新分支。
若來源分支與目標分支不同,直接以目前分支作為 PR 的 `head`。 若來源分支與目標分支不同,直接以目前分支作為 PR 的 `head`。
### E3. 解析 repo 座標 ### E3. 解析 repo 座標
從 `git remote get-url origin` 解析出 **host/owner/repo**(例:`https://gitea.jsc.idv.tw/plugins/code-review.git` → host=`gitea.jsc.idv.tw`、owner=`plugins`、repo=`code-review`)。 依 `/jsc-shared:spec-pull-request`「解析 origin 座標」從 `git remote get-url origin` 解析出 host/owner/repo。
### E4. 決定 PR 描述形式(完整版/簡單版/使用者輸入) ### E4. 決定 PR 描述形式(完整版/簡單版/使用者輸入)
依 `--pr-desc` 三選一;若未提供則預設使用 `full`,不要為描述形式中斷詢問: 依 `--pr-desc` 三選一,各形式的內容與產生方式依 `/jsc-shared:spec-pull-request`「PR 描述模式」執行;未提供時預設 `full`,不要為描述形式中斷詢問。PR **標題**預設取一句總結(可用首個 feat/fix commit 或分支用途);使用者另有指定則從之。
- **完整版(`full`)**:**重新分析並總結** `git diff <target>...<PR 來源分支>`(比對 source 自分岔點以來的變更),整理成結構化繁體中文說明 —— 變更摘要、影響範圍、重點檔案/模組、風險或注意事項。不是貼原始 diff,而是「人讀得懂的總結」。
- **簡單版(`simple`)**:直接把本分支領先 target 的 commit 訊息**逐條列出**:
```bash
git log --oneline "<target>..<PR 來源分支>"
```
以條列呈現每行 commit 訊息。
- **使用者輸入**:採用使用者提供的描述文字。
PR **標題**預設取一句總結(可用首個 feat/fix commit 或分支用途);使用者另有指定則從之。
產生 PR body 時必須使用實際換行與 Markdown 內容,不可把跳脫字串當作正文送出。若用 shell / `jq` 組 JSON,應以 UTF-8 暫存檔、heredoc、`printf` 或 `jq --rawfile` 帶入內容;避免讓 PR 顯示成 `## Commit\n\n- ...` 這類字面 `\n`。
### E5. 呼叫 Gitea API 建立 PR(使用 token) ### E5. 呼叫 Gitea API 建立 PR(使用 token)
token 從環境變數讀取,呼叫 Gitea 建立 PR: 依 `/jsc-shared:spec-pull-request`「已有相同 head→base 的 open PR 時沿用不重開」與「建立 PR:body 一律用 UTF-8 檔案帶入」執行:先查詢目標分支是否已有相同來源的 open PR,有則沿用不重複建立;沒有才呼叫 `POST /pulls`,body 以 UTF-8 檔帶入、換行為實際換行;成功取回應中的 PR 連結/編號回報使用者,失敗時顯示錯誤訊息(先依 `/jsc-shared:spec-gitea` 遮蔽 token)供排查。
```bash ### E6. 完成通知+清除對話內文(重要:可能含 token)
# 不可 echo 含 token 的指令;body 以 UTF-8 JSON 檔帶入,輸出時遮蔽 token
# PR 描述內的換行必須是實際換行,不可使用字面 \n
curl -sS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"https://<host>/api/v1/repos/<owner>/<repo>/pulls" \
--data @body.json
```
- **成功**:取回應中的 PR 連結/編號回報使用者。 依 `/jsc-shared:spec-pull-request`「完成後提醒清除對話內文」通知使用者 push 結果、PR 連結/編號與描述形式;因 push/API 過程可能使對話內文殘留 gitea token,完成後依 `/jsc-shared:spec-gitea`「Token 機密保護」提醒清除對話內文/上下文,清除前再次確認輸出與 log 中沒有任何明文 token。
- **失敗**:顯示 API 回應的錯誤訊息供排查(**先遮蔽 token**)。常見錯誤:目標分支不存在、已有相同 head→base 的 PR、token 權限不足。
### E6. 完成通知 + 清除對話內文(重要:可能含 token)
1. **通知使用者**:push 結果、PR 連結/編號、PR 描述採用哪種形式。
2. **清除 AI 助理對話內文**:因為 push/API 過程可能使對話內文殘留 gitea token,**完成後務必清除對話內文/上下文**以免外洩:
- Claude Code:提示使用者執行 `/clear`(或依當前助理對等指令清空對話)。
- 其他助理:執行各自清除對話/上下文的方式。
- 在清除前,請再次確認輸出與 log 中沒有任何明文 token。
--- ---
@@ -543,11 +407,11 @@ curl -sS -X POST \
## 呼叫方式 ## 呼叫方式
格式:`[--findings <路徑>] [--target <目標分支>] [--pr-desc <full|simple|自訂文字>] [--no-commit] [--no-pr] [--yes]` — 各助理呼叫一個 skill 的方式依 `/jsc-shared:spec-skill-invocation`;本 skill 參數格式與範例:
格式:`[--issue <編號|編號清單|all>] [--findings <路徑>] [--target <目標分支>] [--pr-desc <full|simple|自訂文字>] [--no-commit] [--no-pr] [--yes]` —
除 `--target` 外皆可省略(findings 一律讀目錄 `.gitea/ai-review/findings/` 下所有 wrapper `*.json` 與 `findings.json`;`--issue` 無/單選/多選/全部 額外從 Gitea 議題取問題,與 findings 合併去重;目標分支省略時必問、不猜測)。token 一律由環境變數(如 `GITEA_TOKEN`)提供。 除 `--target` 外皆可省略(findings 一律讀目錄 `.gitea/ai-review/findings/` 下所有 wrapper `*.json` 與 `findings.json`;`--issue` 無/單選/多選/全部 額外從 Gitea 議題取問題,與 findings 合併去重;目標分支省略時必問、不猜測)。token 一律由環境變數(如 `GITEA_TOKEN`)提供。
| 助理 | 呼叫 | - Claude Code / Antigravity:`/jsc-code:review-resolve`,或 `/jsc-code:review-resolve --target develop --pr-desc full --yes`、`/jsc-code:review-resolve --issue 12,15 --target master`、`/jsc-code:review-resolve --issue all --target master`、`/jsc-code:review-resolve --no-pr`、`/jsc-code:review-resolve --no-commit`
| --- | --- | - Codex:`$review-resolve`,或 `$review-resolve --target develop --pr-desc full --yes`、`$review-resolve --issue all --target master`,或用 `/skills` 選單
| Claude Code / Antigravity | `/jsc-code:review-resolve`,或 `/jsc-code:review-resolve --target develop --pr-desc full --yes`、`/jsc-code:review-resolve --issue 12,15 --target master`、`/jsc-code:review-resolve --issue all --target master`、`/jsc-code:review-resolve --no-pr`、`/jsc-code:review-resolve --no-commit` | - OpenCode:描述需求(如「讀 .gitea/ai-review/findings/ 目錄與 findings.json 的 wrapper findings,再併入議題 #12、#15 的 code review 問題去重後依嚴重度逐條處理,更新 findings/exclusions,把工作區變更依 conventional commit 分類提交,push 後對 develop 發 PR(完整版描述)」)自動觸發
| Codex | `$review-resolve`,或 `$review-resolve --target develop --pr-desc full --yes`、`$review-resolve --issue all --target master`,或用 `/skills` 選單 |
| OpenCode | 描述需求(如「讀 .gitea/ai-review/findings/ 目錄與 findings.json 的 wrapper findings,再併入議題 #12、#15 的 code review 問題去重後依嚴重度逐條處理,更新 findings/exclusions,把工作區變更依 conventional commit 分類提交,push 後對 develop 發 PR(完整版描述)」)自動觸發 |
+14 -33
View File
@@ -18,14 +18,11 @@ argument-hint: "[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>]
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-gitea`、`spec-git-safety`、`spec-ask-user`、`spec-skill-invocation`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
- `/jsc-shared:spec-gitea`:`GITEA_TOKEN` 機密保護(不 echo、遮蔽、clone 後還原乾淨 origin)、API 分頁完整讀取、host 決定順序。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(未提交變更不強切、絕不 `reset --hard`/`clean`)、develop → master 後備、`pull --ff-only`。
本 skill 特有補充: 本 skill 特有補充:
@@ -71,18 +68,7 @@ argument-hint: "[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>]
### B1. 分頁取回可讀專案 ### B1. 分頁取回可讀專案
以 token 呼叫 `GET /api/v1/user/repos`(回傳 token 對應使用者**有權限存取**的 repo:自有、組織成員、協作者),**逐頁抓到取回筆數不足一頁為止**(每頁上限以 API 為準,常見 `limit=50`): 依 `/jsc-shared:spec-gitea`「API 呼叫慣例」的分頁完整讀取與 401/403 錯誤處理方式,以 token 呼叫 `GET /api/v1/user/repos`(回傳 token 對應使用者**有權限存取**的 repo:自有、組織成員、協作者),持續累加 `page` 直到某頁回傳筆數不足一頁(或回空陣列)為止,確保**取得全部**而非只第一頁。
```bash
# 不可印出含 token 的指令;token 以 Header 帶入,輸出時遮蔽
page=1
curl -sS \
-H "Authorization: token ${GITEA_TOKEN}" \
"https://<host>/api/v1/user/repos?page=${page}&limit=50"
```
- 持續累加 `page` 直到某頁回傳筆數 `< limit`(或回空陣列)為止,確保**取得全部**而非只第一頁。
- API 失敗(401/403/網路錯誤)→ 回報錯誤(**遮蔽 token**)並停止;401/403 多半是 token 失效或權限不足。
### B2. 篩選與正規化 ### B2. 篩選與正規化
@@ -107,11 +93,8 @@ curl -sS \
選擇方式: 選擇方式:
- **帶 `--owner` 參數** → 直接採用其中的擁有者,**跳過詢問**;清單中沒有的擁有者回報並略過。 - **帶 `--owner` 參數** → 依 `/jsc-shared:spec-ask-user`「已從其他管道得知答案時要跳過詢問」,直接採用其中的擁有者,**跳過詢問**;清單中沒有的擁有者回報並略過。
- **未帶 `--owner`** → 這是**不可忽略的必要決策**,必須讓使用者**多選**: - **未帶 `--owner`** → 依 `/jsc-shared:spec-ask-user` 決定呈現方式(擁有者數量 ≤ 4 用 `AskUserQuestion` 多選,超過 4 改文字列出並支援編號/名稱回覆、`all`/`全部` 代表全選),這是**不可忽略的必要決策**;解析使用者回覆為擁有者集合,無法對應的輸入回報並請使用者重選,**不臆測**。
- 擁有者數量 ≤ 4 時,可用助理的多選提問元件(如 Claude Code 的多選問題)。
- 擁有者較多時,請使用者直接以**編號或擁有者名稱**回覆(支援多個,以逗號或空白分隔),並支援回覆 `all`/`全部` 代表全選。
- 解析使用者回覆為擁有者集合;無法對應的輸入請回報並請使用者重選,**不臆測**。
選定後輸出「將同步的擁有者:…」再進入階段 D。 選定後輸出「將同步的擁有者:…」再進入階段 D。
@@ -143,13 +126,11 @@ mkdir -p "<根目錄>/<擁有者>"
### E2. 專案不存在 → clone ### E2. 專案不存在 → clone
- **HTTPS(預設)**:以 token 組 clone URL,**用變數帶入、不可印出**;clone 完成後**還原乾淨 origin**: - **HTTPS(預設)**:依 `/jsc-shared:spec-gitea`「Token 機密保護」以 token 組 clone URL(用變數帶入、不可印出),clone 完成後還原成不含 token 的乾淨 origin:
```bash ```bash
dest="<根目錄>/<擁有者>/<專案名>" dest="<根目錄>/<擁有者>/<專案名>"
# GITEA_TOKEN 來自環境變數;clone_url 形如 https://<host>/<owner>/<repo>.git
git clone "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" "${dest}" git clone "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" "${dest}"
# 還原成不含 token 的乾淨 URL,避免 token 落地在 .git/config
git -C "${dest}" remote set-url origin "https://<host>/<owner>/<repo>.git" git -C "${dest}" remote set-url origin "https://<host>/<owner>/<repo>.git"
``` ```
@@ -209,11 +190,11 @@ git -C "${dest}" fetch --all --prune
## 呼叫方式 ## 呼叫方式
格式:`[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>] [--host <gitea 主機>] [--include-forks] [--ssh] [--yes]` — 依 `/jsc-shared:spec-skill-invocation` 的呼叫方式表格;格式:`[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>] [--host <gitea 主機>] [--include-forks] [--ssh] [--yes]` —
全部可省略(根目錄預設家目錄;未帶 `--owner` 時於階段 C 詢問多選)。token 一律由環境變數(如 `GITEA_TOKEN`)提供,不寫死、不印出。 全部可省略(根目錄預設家目錄;未帶 `--owner` 時於階段 C 詢問多選)。token 一律由環境變數(如 `GITEA_TOKEN`)提供,不寫死、不印出。
| 助理 | 呼叫 | 範例:
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:sync`,或 `/jsc-code:sync --owner plugins --target-dir ~/work`、`/jsc-code:sync --include-forks --ssh` | - Claude Code / Antigravity:`/jsc-code:sync`,或 `/jsc-code:sync --owner plugins --target-dir ~/work`、`/jsc-code:sync --include-forks --ssh`
| Codex | `$sync`,或 `$sync --owner plugins --target-dir ~/work`,或用 `/skills` 選單 | - Codex:`$sync`,或 `$sync --owner plugins --target-dir ~/work`,或用 `/skills` 選單
| OpenCode | 描述需求(如「用 GITEA_TOKEN 取得我有讀取權限的所有 Gitea 專案,依擁有者分組讓我多選,把選定擁有者的專案 clone 到家目錄;已存在的切到 develop/master 更新到最新」)自動觸發 | - OpenCode:描述需求(如「用 GITEA_TOKEN 取得我有讀取權限的所有 Gitea 專案,依擁有者分組讓我多選,把選定擁有者的專案 clone 到家目錄;已存在的切到 develop/master 更新到最新」)自動觸發
+30 -79
View File
@@ -19,15 +19,11 @@ argument-hint: "[--schedule on|off] [--run] [--project-dir <專案根目錄>] [-
--- ---
## 共用規範(shared plugin,必要前置) ## 共用規範(必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin(`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行: 先載入 `/jsc-shared:spec-preflight` 並依其流程處理;載入不到即代表 shared plugin 未安裝,
依該 spec 詢問使用者是否安裝 `https://gitea.jsc.idv.tw/plugins/shared.git`,不安裝則中斷本 skill。
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼(含 `TARGET.md` 回寫、commit 訊息、PR 標題/描述)、表格呈現。 本 skill 需要的規範:`spec-output`、`spec-execution`、`spec-git-safety`、`spec-gitea`、`spec-time-log`、`spec-todo-list`、`spec-conventional-commit`、`spec-git-push`、`spec-pull-request`、`spec-skill-invocation`、`spec-model`
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard`/`clean`)、`pull --ff-only`、保守解衝突、新分支不覆蓋既有分支。
- `/jsc-shared:spec-gitea`:`GITEA_TOKEN` 機密保護(不 echo、遮蔽 `***`)、API body 以 UTF-8 JSON 檔帶入。
- `/jsc-shared:spec-time-log`:Asia/Taipei `yyyy/MM/dd HH:mm:ss` 時間格式、`[時間][階段][等級]: 訊息` log 格式。
本 skill 特有補充: 本 skill 特有補充:
@@ -45,7 +41,7 @@ argument-hint: "[--schedule on|off] [--run] [--project-dir <專案根目錄>] [-
- `--run`:立即執行一次 `TARGET.md`(排程條目帶的就是這個)。 - `--run`:立即執行一次 `TARGET.md`(排程條目帶的就是這個)。
- `--project-dir <專案根目錄>`:目標專案根目錄,**省略時取目前工作目錄**(會先 `git rev-parse --show-toplevel` 正規化成 repo 根)。`TARGET.md` 固定讀**專案根目錄**的那一份。 - `--project-dir <專案根目錄>`:目標專案根目錄,**省略時取目前工作目錄**(會先 `git rev-parse --show-toplevel` 正規化成 repo 根)。`TARGET.md` 固定讀**專案根目錄**的那一份。
- `--cli <claude|codex|agy|opencode|copilot>`:排程條目要用哪個助理的 headless 指令。省略時用**目前正在執行本 skill 的助理**;判斷不出來且未帶此參數 → 詢問,不猜測。 - `--cli <claude|codex|agy|opencode|copilot>`:排程條目要用哪個助理的 headless 指令。省略時用**目前正在執行本 skill 的助理**;判斷不出來且未帶此參數 → 詢問,不猜測。
- `--base <PR 目標分支>`:PR 的目標分支。**省略時自動取遠端預設分支**(見 F1),取不到才詢問。 - `--base <PR 目標分支>`:PR 的目標分支。**省略時自動取遠端預設分支**(見階段 F),取不到才詢問。
- `--no-pr`:執行到 push 為止,不開 PR。 - `--no-pr`:執行到 push 為止,不開 PR。
- `--yes`:全自動,不做確認式詢問(必要決策仍會中斷)。 - `--yes`:全自動,不做確認式詢問(必要決策仍會中斷)。
@@ -66,15 +62,16 @@ argument-hint: "[--schedule on|off] [--run] [--project-dir <專案根目錄>] [-
- [x] 移除已停用的舊設定檔(2026/08/02 23:41:07 完成) - [x] 移除已停用的舊設定檔(2026/08/02 23:41:07 完成)
``` ```
checklist 語法、已完成略過不重做、子項目連動、標題/說明文字原樣保留、禁止憑空編造等格式規則依 `/jsc-shared:spec-todo-list` 執行。
本 skill 特有:
| 規則 | 說明 | | 規則 | 說明 |
| --- | --- | | --- | --- |
| 待辦 | `- [ ]` 未勾選的項目,**由上而下**依序處理 | | 待辦 | `- [ ]` 未勾選的項目,**由上而下**依檔案既有順序處理(與 `spec-todo-list` 建立議題 TODO 時「依影響範圍排序」的情境不同,此處沿用 `TARGET.md` 本身的行順序) |
| 已完成 | `- [x]` 直接略過,不重做 |
| 子項目 | 縮排的 `- [ ]` 視為上層項目的子步驟,隨上層一起處理;子項目全部完成才勾選上層 |
| 標題/說明文字 | 原樣保留,只當作項目的背景脈絡,不改寫 |
| 檔案不存在 | `--run` 視為「無待辦」正常結束(不建檔、不報錯);`--schedule on` 則提醒尚無 `TARGET.md`,非 `--yes` 時詢問是否仍要建立排程 | | 檔案不存在 | `--run` 視為「無待辦」正常結束(不建檔、不報錯);`--schedule on` 則提醒尚無 `TARGET.md`,非 `--yes` 時詢問是否仍要建立排程 |
完成一項後就地改成 `- [x]`,並在該行**行尾**補上 `(<yyyy/MM/dd HH:mm:ss> 完成)`(Asia/Taipei)。無法安全完成的項目**保持未勾選**,並在該行下方以縮排補一行 `> ⏭️ 待人工處理:<原因>`(已有同樣註記則更新而非重複新增)。 完成一項後就地改成 `- [x]`,並在該行**行尾**補上 `(<yyyy/MM/dd HH:mm:ss> 完成)`(Asia/Taipei,時間格式依 `/jsc-shared:spec-time-log`)。無法安全完成的項目**保持未勾選**,並在該行下方以縮排補一行 `> ⏭️ 待人工處理:<原因>`(已有同樣註記則更新而非重複新增)。
--- ---
@@ -189,32 +186,22 @@ git status --porcelain
git rev-parse --abbrev-ref HEAD git rev-parse --abbrev-ref HEAD
``` ```
- **工作區有未提交變更** → **停止**並回報(排程情境寫入 log)。理由:本 skill 結尾會分類提交所有變更,混入使用者未完成的工作會把它一起提交上去。依 `/jsc-shared:spec-git-safety`,**不得** stash、`reset --hard` 或 `clean` 來清場。 - **工作區有未提交變更** → **停止**並回報(排程情境寫入 log)。理由:本 skill 結尾會分類提交所有變更,混入使用者未完成的工作會把它一起提交上去。依 `/jsc-shared:spec-git-safety`「不破壞既有工作」,不得 stash、`reset --hard` 或 `clean` 來清場。
- **detached HEAD** → 停止並回報。 - **detached HEAD** → 停止並回報。
### A2. Fetch 與判定遠端預設分支 ### A2. Fetch 與判定遠端預設分支
```bash ```bash
git fetch --all --prune git fetch --all --prune
git symbolic-ref --quiet refs/remotes/origin/HEAD # → refs/remotes/origin/<預設分支>
``` ```
取不到時退而用 `git remote show origin`(找 `HEAD branch:` 那行);`--base` 有帶則直接採用。兩者都不成立 → 停止並詢問,不臆測 `master`/`main`。 依 `/jsc-shared:spec-git-safety`「工作分支選擇」判定遠端預設分支(`git symbolic-ref refs/remotes/origin/HEAD` → 取不到退而 `git remote show origin` → 兩者皆無則停止詢問,不臆測 `master`/`main`)。**本 skill 特有**:帶 `--base` 時直接採用 `--base` 的值,不再另行判定。
### A3. 切到 `develop`(不存在就從預設分支建立) ### A3. 切到 `develop`(不存在就從預設分支建立)
```bash 依 `/jsc-shared:spec-git-safety`「develop → master 後備分支」與「工作分支選擇」處理:`origin/develop` 存在則切換並 `git pull --ff-only`(分岔失敗 → 停止並回報,不做 merge/rebase 猜測);不存在則從 A2 判定出的遠端預設分支建立 `develop`。
if git rev-parse --verify --quiet origin/develop >/dev/null; then
git switch develop 2>/dev/null || git switch -c develop --track origin/develop
git pull --ff-only
else
git switch -c develop "origin/<預設分支>" # 遠端沒有 develop → 從預設分支開一條新的
fi
```
- `pull --ff-only` 失敗(分岔)→ 停止並回報,交由使用者處理,不做 merge/rebase 猜測。 本 skill 特有:**本地已有 `develop` 但遠端沒有** → 沿用本地那條,**不覆蓋、不重建**,並在回報中說明它還沒推上遠端(`git switch develop 2>/dev/null || git switch -c develop --track origin/develop` 的寫法即可自然落在此情形)。新建 `develop` 屬不可忽略的狀態變更,必須在回報與 log 中明講「遠端沒有 develop,已從 `<預設分支>` 建立」。
- 本地已有 `develop` 但遠端沒有 → 沿用本地那條,**不覆蓋、不重建**;並在回報中說明它還沒推上遠端。
- 新建 develop 屬不可忽略的狀態變更,必須在回報與 log 中明講「遠端沒有 develop,已從 `<預設分支>` 建立」。
--- ---
@@ -228,6 +215,8 @@ fi
| # | 項目 | 子項目數 | 備註 | | # | 項目 | 子項目數 | 備註 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
若 `TARGET.md` 帶有 `model:` frontmatter,依 `/jsc-shared:spec-model` 的『讀到帶 `model:` frontmatter 的清單檔時的檢查義務』先做模型檢查。
--- ---
## 階段 C:逐項實作並回寫 `TARGET.md` ## 階段 C:逐項實作並回寫 `TARGET.md`
@@ -246,77 +235,37 @@ fi
## 階段 D:分類提交 ## 階段 D:分類提交
1. 盤點**所有**變更(含未追蹤檔): 依 `/jsc-shared:spec-conventional-commit` 執行:以 `git status --porcelain=v1 -uall` 完整盤點所有變更(含未追蹤檔)、依實際異動內容歸入 9 種 commit 類型(每個 type 各一個 commit,訊息格式 `type(範圍): 一句總結`,範圍不得重述 type 本身)、逐組精準 `git add -- <該組檔案...>` 後分別 commit(不用 `git add -A`)。
```bash 本 skill 特有:
git status --porcelain=v1 -uall
```
2. 依實際異動內容歸入 conventional commit 類型(`feat`/`fix`/`docs`/`style`/`refactor`/`perf`/`test`/`chore`/`revert`),**每個 type 各一個 commit**。 - `TARGET.md` 本身的勾選更新歸 `chore`,訊息固定用 `chore(TARGET): 更新每日待辦完成狀態`。
3. commit 訊息格式 `type(範圍): 一句總結`,**括號內是實際異動的功能/模組名**,不是重述 type: - 無任何變更(例如所有項目都待人工處理,只有 `TARGET.md` 的註記異動)→ 仍照常提交 `TARGET.md` 的異動;連 `TARGET.md` 都沒變則跳過 D、E、F,直接進入總結。
- ✅ `feat(使用者服務): 補上帳號建立與停用的單元測試`、`docs(README): 更新安裝章節的 CLI 指令`
- ❌ `feat(新增功能)`、`docs(文件)`
- `TARGET.md` 本身的勾選更新歸 `chore`,訊息用 `chore(TARGET): 更新每日待辦完成狀態`。
4. 逐組精準 `git add -- <該組檔案...>` 後 commit,不用 `git add -A` 一次混提。
5. 無任何變更(例如所有項目都待人工處理,只有 `TARGET.md` 的註記異動)→ 仍照常提交 `TARGET.md` 的異動;連 `TARGET.md` 都沒變則跳過 D、E、F,直接進入總結。
> 分類與訊息規則與 `/jsc-code:review-resolve` 階段 C 一致,細節可參照該 skill。
--- ---
## 階段 E:Push `develop` ## 階段 E:Push `develop`
依 `/jsc-shared:spec-git-push` 的三段式流程(認證管理器 → token push → 詢問使用者,前者失敗才換下一個,token 全程遮蔽且不寫進 remote 設定)推送 `develop`。推送前先確認是否有領先遠端的 commit:
```bash ```bash
git log --oneline "origin/develop..develop" 2>/dev/null # 領先遠端的 commit;遠端無 develop 時視為全部要推 git log --oneline "origin/develop..develop" 2>/dev/null # 領先遠端的 commit;遠端無 develop 時視為全部要推
``` ```
無 commit 可推 → 跳過 push,直接進入階段 F(既有遠端分支仍可開 PR)。否則依序嘗試,前者失敗才換下一個: 本 skill 特有:無 commit 可推 → 跳過 push,直接進入階段 F(既有遠端分支仍可開 PR);三段皆失敗 → 停止並回報失敗原因(先遮蔽 token),排程情境寫入 log,等使用者處理,不猜測其他憑證。
1. **認證管理器**:`git push -u origin develop`
2. **token**:從環境變數帶入,指令與輸出**全程遮蔽 token、不寫進 remote 設定**:
```bash
git push "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" develop
```
3. **都失敗** → 停止並回報失敗原因(先遮蔽 token)。排程情境寫入 log,等使用者處理,不猜測其他憑證。
--- ---
## 階段 F:對預設分支發 PR(`--no-pr` 時略過) ## 階段 F:對預設分支發 PR(`--no-pr` 時略過)
### F1. 目標分支 依 `/jsc-shared:spec-pull-request` 執行:目標分支不臆測 → 解析 origin 座標(host/owner/repo)→ 先查有沒有相同 head=`develop` → base=`<目標分支>` 的 open PR,有則沿用不重開(今天新推的 commit 會自動出現在該 PR,可視情況補一則今日進度留言)→ 沒有才以 UTF-8 JSON 檔帶入 body 建立新 PR(換行用實際換行,不可送出字面 `\n`)→ 失敗回報 API 錯誤訊息(先依 `/jsc-shared:spec-gitea` 遮蔽 token)→ 完成後提醒清除對話內文。
`--base` 有帶則用它,否則用 A2 判定出的**遠端預設分支**。若預設分支就是 `develop`(等於 head=base)→ 不開 PR,回報原因即可。 本 skill 特有:
### F2. 先查有沒有現成的 open PR(每天跑,不可重複開)
```bash
curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"https://<host>/api/v1/repos/<owner>/<repo>/pulls?state=open&base=<預設分支>"
```
已存在 `head=develop` → `base=<預設分支>` 的 open PR → **沿用它**(今天新推的 commit 會自動出現在該 PR),回報既有 PR 連結,並視情況在該 PR 補一則今日進度留言;**不重複建立**。
### F3. 建立 PR
repo 座標由 `git remote get-url origin` 解析(host/owner/repo)。body 以 UTF-8 JSON 檔帶入,換行用實際換行、不可送出字面 `\n`:
```bash
curl -sS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"https://<host>/api/v1/repos/<owner>/<repo>/pulls" \
--data @body.json
```
- **目標分支**:`--base` 有帶則用它,否則用 A2 判定出的遠端預設分支;若該目標分支就是 `develop`(等於 head=base)→ 不開 PR,回報原因即可。
- **標題**:`chore(TARGET): <yyyy/MM/dd> 每日待辦`(有明確主軸時可改成該主軸的一句總結)。 - **標題**:`chore(TARGET): <yyyy/MM/dd> 每日待辦`(有明確主軸時可改成該主軸的一句總結)。
- **描述**:本次完成的項目清單(✅/⏭️ 各自列出+待人工處理原因)、變更摘要與影響範圍、本次的 commit 一覽。 - **描述**:本次完成的項目清單(✅/⏭️ 各自列出+待人工處理原因)、變更摘要與影響範圍、本次的 commit 一覽。
- 失敗 → 回報 API 錯誤訊息(先遮蔽 token)。常見原因:目標分支不存在、已有相同 head→base 的 PR(回到 F2 沿用)、token 權限不足。 - **收尾**:push/API 過程可能讓 token 殘留在對話內文;**互動情境**完成後提醒使用者清除對話(Claude Code 用 `/clear`)。**排程情境**沒有互動對話,只寫 log,不輸出 token。
### F4. 收尾
push/API 過程可能讓 token 殘留在對話內文;**互動情境**完成後提醒使用者清除對話(Claude Code 用 `/clear`)。排程情境只寫 log,不輸出 token。
--- ---
@@ -335,6 +284,8 @@ push/API 過程可能讓 token 殘留在對話內文;**互動情境**完成
## 呼叫方式 ## 呼叫方式
依 `/jsc-shared:spec-skill-invocation` 的統一呼叫方式,本 skill 的實際參數格式與範例:
| 助理 | 呼叫 | | 助理 | 呼叫 |
| --- | --- | | --- | --- |
| Claude Code / Antigravity | `/jsc-code:target --schedule on`、`/jsc-code:target --schedule off`、`/jsc-code:target --run --yes`、`/jsc-code:target --schedule on --run --project-dir ~/work/app` | | Claude Code / Antigravity | `/jsc-code:target --schedule on`、`/jsc-code:target --schedule off`、`/jsc-code:target --run --yes`、`/jsc-code:target --schedule on --run --project-dir ~/work/app` |