release: v0.1.9 develop 到 master #29

Merged
admin merged 8 commits from develop into master 2026-08-27 09:01:07 +00:00
8 changed files with 337 additions and 6 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.1.8", "version": "0.1.9",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.1.8", "version": "0.1.9",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills" "skills": "./skills"
} }
+14 -1
View File
@@ -23,7 +23,8 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| 工具 | 用途 | | 工具 | 用途 |
| --- | --- | | --- | --- |
| `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) | | `tools/detect-clis.sh` | 列出已安裝的 AI CLI 與執行檔路徑(TSV:name / path / version;antigravity 的執行檔為 `agy`、kiro 為 `kiro-cli`) |
| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 | | `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。install 或 update 全數成功時,收尾轉呼叫 `jsc-hooks` 的 `restart-gate.sh require` 掛上重啟閘門,並印一行 `restart` 標出狀態檔位置;uninstall 不寫。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
| `tools/write-guides.sh` | 產生這台機器專屬的更新指引 `$JSC_HOME/update-guide.md` 與移除指引 `$JSC_HOME/remove-guide.md`(`write-guides.sh [-n] {install\|update} {domain}...`),一輪部署跑一次。CLI 清單取自 `detect-clis.sh`,每支 CLI 的指令字面直接取自 `deploy.sh -n` 的輸出,所以指引寫的就是實際會跑的指令;kiro 走不走本地複製退路也依實際偵測結果標注 |
| `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 | | `tools/list-models.sh` | 讀各 CLI 設定檔列出模型(TSV:cli / model / in-use);設定檔缺失就不輸出該 CLI 的列,一律 exit 0。設定檔位置只寫在這支腳本裡 |
| `tools/model-config.sh` | 解析 SDLC 各階段的偏好模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md`。鏈只影響建議與偏好順序,不影響閘門放行 | | `tools/model-config.sh` | 解析 SDLC 各階段的偏好模型鏈(`get {stage}`、`list`、`resolve {stage}` 印出目前 CLI 可用的第一個模型);專案 `.jsc/models` 優先於 `$JSC_HOME/models.conf`,格式見 `references/model-tags.md`。鏈只影響建議與偏好順序,不影響閘門放行 |
| `tools/config-spec.tsv` | 設定規格表:每個環境變數與設定檔一列,標明必要或選擇、預設值、驗證方式、修法。體檢與設定共用這一份,新增設定時要同步補一列 | | `tools/config-spec.tsv` | 設定規格表:每個環境變數與設定檔一列,標明必要或選擇、預設值、驗證方式、修法。體檢與設定共用這一份,新增設定時要同步補一列 |
@@ -70,9 +71,21 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `JSC_DEPLOY_DRYRUN` | 設為 `1` 等同 `deploy.sh -n`,只印指令不執行 | 照常執行 | | `JSC_DEPLOY_DRYRUN` | 設為 `1` 等同 `deploy.sh -n`,只印指令不執行 | 照常執行 |
| `JSC_WIKI_REPO_CHECK` | 體檢頁 `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`;兩個都沒有就略過寫入,並把這一項列進待修 | | `JSC_WIKI_REPO_CHECK` | 體檢頁 `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`;兩個都沒有就略過寫入,並把這一項列進待修 |
| `JSC_CONFIG_SPEC` | 改讀別份設定規格表(測試 `scan-config.sh` 時用) | 用 `tools/config-spec.tsv` | | `JSC_CONFIG_SPEC` | 改讀別份設定規格表(測試 `scan-config.sh` 時用) | 用 `tools/config-spec.tsv` |
| `JSC_HOME` | hook 資料目錄,兩份指引與重啟狀態檔都寫在這裡 | 用 `~/.jsc` |
| `JSC_RESTART_GATE` | 設成 `off` 可略過部署後的重啟提示閘門(判讀在 `jsc-hooks`,`jsc-cli` 只負責寫狀態檔) | 照常提示重啟 |
`JSC_LOCAL_PLUGINS` 的預設值刻意避開 `~/plugins`:那是維護者放技能組開發 checkout 的地方,`git pull` 下去會蓋掉未提交的工作。這個變數指到的目錄若是開發中的樹(有未提交變更,或有未推送的 commit),`deploy.sh` 只印一行 `skip` 並直接用現地內容安裝,不執行 `git pull`。 `JSC_LOCAL_PLUGINS` 的預設值刻意避開 `~/plugins`:那是維護者放技能組開發 checkout 的地方,`git pull` 下去會蓋掉未提交的工作。這個變數指到的目錄若是開發中的樹(有未提交變更,或有未推送的 commit),`deploy.sh` 只印一行 `skip` 並直接用現地內容安裝,不執行 `git pull`。
## 部署留在機器上的檔案
| 檔案 | 何時產生 | 用途 |
| --- | --- | --- |
| `$JSC_HOME/update-guide.md` | install、update 收尾 | 下次更新的依據:偵測到的 CLI、每支的安裝方式與實際指令、marketplace token、domain 清單 |
| `$JSC_HOME/remove-guide.md` | install、update 收尾 | 整組移除的依據:各 CLI 的移除指令,加上 `$JSC_HOME`、本地 clone、kiro 技能目錄、rc 檔 `# jsc-config` 段落這些殘留物 |
| `$JSC_HOME/restart-required` | install、update 全數成功時 | 一行一次成功部署(時間戳、CLI、模式、domain 清單)。`jsc-hooks` 讀它提示使用者重啟工作階段,逃生門 `JSC_RESTART_GATE=off` 也在那邊判讀 |
兩份指引一律整份覆寫,內容依產生當下的偵測結果生成,不寫死。`uninstall` 不產生指引、也不寫重啟狀態檔。
## 相關 domain ## 相關 domain
- [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):取得 domain 名單(`tools/gitea.sh repos plugins`) - [`jsc-gitea`](https://gitea.jsc.idv.tw/plugins/gitea):取得 domain 名單(`tools/gitea.sh repos plugins`)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc-cli", "name": "jsc-cli",
"version": "0.1.8", "version": "0.1.9",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署", "description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills/" "skills": "./skills/"
} }
+4 -2
View File
@@ -1,6 +1,6 @@
--- ---
name: deploy name: deploy
description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs via detect-clis.sh, report every plugin's local-versus-published version first and recommend update when any one of them is behind, then ask the user for the mode via decision tree, then run each CLI's native plugin commands with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. Use for rollout or removal of the jsc plugins; not for a single skill. description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs via detect-clis.sh, report every plugin's local-versus-published version first and recommend update when any one of them is behind, then ask the user for the mode via decision tree, then run each CLI's native plugin commands with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. After install or update, write this machine's update and remove guides via write-guides.sh and demand a session restart. Use for rollout or removal of the jsc plugins; not for a single skill.
--- ---
# deploy — batch install, update, or uninstall the skill set # deploy — batch install, update, or uninstall the skill set
@@ -23,4 +23,6 @@ description: Batch install, update, or uninstall the whole jsc skill set on ever
The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. Done when the domain list comes from that response and holds at least one name. The marketplace is unified as `jsc`; the install token is `jsc-{domain}@jsc`. Each `plugins[].name` already carries the `jsc-` prefix (e.g. `jsc-ask`) — pass it to `tools/deploy.sh` as-is, prefixed or not; the script normalizes it. Done when the domain list comes from that response and holds at least one name.
5. Run `tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent** (one sub agent per CLI). The script prints `cmd` and `exit` lines for every command, then one `result` line; `-n` prints the commands without running them. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content. Done when every detected CLI has reported an exit status for every command it ran. 5. Run `tools/deploy.sh {mode} {cli} {domain}...` once per detected CLI, passing the whole domain list in one call so the marketplace command runs only once. This step **MUST run as a sub agent** (one sub agent per CLI). The script prints `cmd` and `exit` lines for every command, then one `result` line; `-n` prints the commands without running them. Antigravity cannot install from a Gitea URL, so the script clones each domain into the local plugin directory (`JSC_LOCAL_PLUGINS`, default `$JSC_HOME/plugins`) and installs from that path — keep that clone, because update pulls the same one. That default deliberately avoids a development checkout: when the directory holds uncommitted changes or unpushed commits, the script prints a `skip` line, leaves the tree untouched, and installs the on-disk content. Done when every detected CLI has reported an exit status for every command it ran.
6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. Done when hooks-install reports a wiring result for each detected CLI. 6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. Done when hooks-install reports a wiring result for each detected CLI.
7. Report the result and any failure reason for every CLI × mode, plus every `skip` line and every CLI that could not be version-checked in step 2. Done when every detected CLI appears in the report with its `result` status. 7. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 5 has finished. It rewrites `$JSC_HOME/update-guide.md` and `$JSC_HOME/remove-guide.md` from the live detection result, so the later update and removal runs have the real commands for this machine. Skip it for `uninstall`: the guides describe an installed skill set. Done when the script printed a `wrote` line for both files.
8. Report the result and any failure reason for every CLI × mode, plus every `skip` line and every CLI that could not be version-checked in step 2. Done when every detected CLI appears in the report with its `result` status.
9. For install or update, close the report with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required` and prints its path on a `restart` line; `jsc-hooks` reads that file and keeps reminding until the restart happens, with `JSC_RESTART_GATE=off` as the escape hatch. Name the two guide paths from step 7 in the same closing block, so the operator knows where this machine's update and removal commands now live. Done when the restart instruction is printed and both guide paths are named.
+9
View File
@@ -31,6 +31,12 @@ JSC_WIKI_REPO_CHECK env global no JSC_WIKI_REPO wiki-repo ask CHECK_CONTENTS、C
JSC_WIKI_REPO_REPORT env global no JSC_WIKI_REPO wiki-repo ask REPORT_CONTENTS、REPORT_{HASH} 所在存取庫,年月週日報表寫在這裡 JSC_WIKI_REPO_REPORT env global no JSC_WIKI_REPO wiki-repo ask REPORT_CONTENTS、REPORT_{HASH} 所在存取庫,年月週日報表寫在這裡
JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前置檢查,離線工作時用 JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前置檢查,離線工作時用
JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數 JSC_VERSION_TTL env global no 600 set ask 版本查詢快取秒數
JSC_RESTART_GATE env global no on set ask 設成 off 可略過部署後的重啟提示閘門,判讀在 jsc-hooks
JSC_WIKI_REPO_SKILLSET env global no JSC_WIKI_REPO wiki-repo ask SKILLSET_CONTENTS、SKILLSET_{HASH} 所在的 {owner}/{repo},技能組異動報告寫在這裡
JSC_LANG_GUARD env global no on set ask 設成 off 可關閉繁中編碼與簡體字守門,誤判時用
JSC_COMMENT_SCOPE env global no on set ask 設成 off 可關閉註解夾帶文件編號的守門,誤判時用
JSC_CHANGED_FILE internal runtime no - none - 非 Claude CLI 傳入的變更檔路徑,註解範圍與繁中編碼守門讀它
JSC_SIMPLIFIED_FILE internal runtime no - none - 自訂簡體字表路徑,ste100-lint.sh 讀它取代內建字表
JSC_PR_WATCH_INTERVAL env global no 60 set ask pr-watch.sh 輪詢 PR 狀態的間隔秒數,實作在 jsc-gitea/tools/pr-watch.sh JSC_PR_WATCH_INTERVAL env global no 60 set ask pr-watch.sh 輪詢 PR 狀態的間隔秒數,實作在 jsc-gitea/tools/pr-watch.sh
JSC_LOCAL_PLUGINS env global no $JSC_HOME/plugins dir auto antigravity 安裝來源的本地 plugin 目錄 JSC_LOCAL_PLUGINS env global no $JSC_HOME/plugins dir auto antigravity 安裝來源的本地 plugin 目錄
JSC_PLUGINS_ROOT env global no - dir ask 技能組工作目錄根位置,jsc-meta 的工具用它找各 domain JSC_PLUGINS_ROOT env global no - dir ask 技能組工作目錄根位置,jsc-meta 的工具用它找各 domain
@@ -42,6 +48,9 @@ JSC_GITEA_TOOLS env global no - dir ask jsc-gitea/tools 的位置,跨 domain
$JSC_HOME/model-tags.tsv file global yes - file auto SDLC 階段閘門讀的能力標籤表,由 /jsc-cli:models 產生 $JSC_HOME/model-tags.tsv file global yes - file auto SDLC 階段閘門讀的能力標籤表,由 /jsc-cli:models 產生
$JSC_HOME/models.conf file global no - file ask 各 SDLC 階段的偏好模型鏈,專案 .jsc/models 可覆寫 $JSC_HOME/models.conf file global no - file ask 各 SDLC 階段的偏好模型鏈,專案 .jsc/models 可覆寫
$JSC_HOME/html-styles.conf file global no - file ask 各類頁面的 HTML 匯出版型與樣式,專案 .jsc/html-styles 可覆寫 $JSC_HOME/html-styles.conf file global no - file ask 各類頁面的 HTML 匯出版型與樣式,專案 .jsc/html-styles 可覆寫
$JSC_HOME/update-guide.md file global no - file manual 這台機器的更新指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
$JSC_HOME/remove-guide.md file global no - file manual 這台機器的移除指引,由 jsc-cli/tools/write-guides.sh 產生;缺了就重跑 /jsc-cli:deploy
$JSC_HOME/restart-required file global no - none - 部署收尾寫下的重啟狀態檔,執行期暫態;不存在代表沒有待重啟的部署,jsc-hooks 讀它提示重啟
.jsc/models file project no - file ask 本專案的 SDLC 偏好模型鏈,覆寫 $JSC_HOME/models.conf .jsc/models file project no - file ask 本專案的 SDLC 偏好模型鏈,覆寫 $JSC_HOME/models.conf
.jsc/html-styles file project no - file ask 本專案的 HTML 匯出版型與樣式,覆寫 $JSC_HOME/html-styles.conf .jsc/html-styles file project no - file ask 本專案的 HTML 匯出版型與樣式,覆寫 $JSC_HOME/html-styles.conf
.claude/settings.json file project no - file manual claude 的專案層設定;jsc hook 由 plugin 的 hooks.json 接線,這裡不該有 jsc 殘留接線 .claude/settings.json file project no - file manual claude 的專案層設定;jsc hook 由 plugin 的 hooks.json 接線,這裡不該有 jsc 殘留接線
1 # config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。
31 JSC_WIKI_REPO_REPORT
32 JSC_VERSION_GUARD
33 JSC_VERSION_TTL
34 JSC_RESTART_GATE
35 JSC_WIKI_REPO_SKILLSET
36 JSC_LANG_GUARD
37 JSC_COMMENT_SCOPE
38 JSC_CHANGED_FILE
39 JSC_SIMPLIFIED_FILE
40 JSC_PR_WATCH_INTERVAL
41 JSC_LOCAL_PLUGINS
42 JSC_PLUGINS_ROOT
48 $JSC_HOME/model-tags.tsv
49 $JSC_HOME/models.conf
50 $JSC_HOME/html-styles.conf
51 $JSC_HOME/update-guide.md
52 $JSC_HOME/remove-guide.md
53 $JSC_HOME/restart-required
54 .jsc/models
55 .jsc/html-styles
56 .claude/settings.json
+43
View File
@@ -10,6 +10,7 @@
# exit<TAB>{結束碼}<TAB>{指令} 該指令的結束碼;dry-run 時結束碼印「-」 # exit<TAB>{結束碼}<TAB>{指令} 該指令的結束碼;dry-run 時結束碼印「-」
# skip<TAB>{domain}<TAB>{原因} 本地 clone 是開發中的樹,略過 git pull # skip<TAB>{domain}<TAB>{原因} 本地 clone 是開發中的樹,略過 git pull
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由) # note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail} # result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、note 不算失敗,但呼叫端要據實回報。 # 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、note 不算失敗,但呼叫端要據實回報。
# marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。 # marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。
@@ -18,7 +19,13 @@
# kiro 的執行檔是 kiro-cli;先探測這個版本認不認得 plugin 子指令(部分版本已經完全 # kiro 的執行檔是 kiro-cli;先探測這個版本認不認得 plugin 子指令(部分版本已經完全
# 移除,例:2.18.1),認得才逐一嘗試、失敗才退回複製,不認得就整批直接走複製, # 移除,例:2.18.1),認得才逐一嘗試、失敗才退回複製,不認得就整批直接走複製,
# 不逐一撞一次「unrecognized subcommand」才退回。 # 不逐一撞一次「unrecognized subcommand」才退回。
# install 或 update 全數成功時,收尾寫一行進 $JSC_HOME/restart-required,代表這台機器有一輪
# 部署還沒重啟。擋人邏輯不在這裡:由 jsc-hooks 讀這個檔案提示使用者重啟,逃生門
# JSC_RESTART_GATE=off 也由那邊判讀。
# 更新指引與移除指引由同目錄的 write-guides.sh 產生,一輪部署跑一次,不在這支腳本裡:
# 這支腳本的職責是「對單一 CLI 部署」,指引寫的是整台機器的樣貌。
# 環境變數: # 環境變數:
# JSC_HOME hook 資料目錄,重啟狀態檔寫在這裡(預設 ~/.jsc)
# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw) # GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw)
# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins) # JSC_GITEA_OWNER 存取庫的 owner(預設 plugins)
# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄 # JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄
@@ -57,6 +64,41 @@ usage() {
exit 2 exit 2
} }
# 找 jsc-hooks 的 restart-gate.sh。順序比照 jsc-sdlc 的 wp-gate.sh:環境變數指定優先,
# 再找並排的工作樹,最後找 plugin 快取。找不到就回非零,由呼叫端據實回報。
restart_gate_sh() {
if [ -n "${JSC_HOOKS_DIR:-}" ] && [ -f "$JSC_HOOKS_DIR/restart-gate.sh" ]; then
printf '%s\n' "$JSC_HOOKS_DIR/restart-gate.sh"; return 0
fi
_root=$(cd "$(dirname "$0")/.." 2>/dev/null && pwd) || return 1
for _c in "$_root/../hooks/hooks/restart-gate.sh" "$_root/../jsc-hooks/hooks/restart-gate.sh"; do
[ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
done
_c=$(ls -d "$HOME"/.claude/plugins/cache/*/jsc-hooks/*/hooks/restart-gate.sh 2>/dev/null | sort | tail -n1)
[ -n "$_c" ] && [ -f "$_c" ] && { printf '%s\n' "$_c"; return 0; }
return 1
}
# 掛上部署後的重啟閘門。狀態檔的路徑、格式與判讀全在 jsc-hooks 的 restart-gate.sh,
# 這裡只轉呼叫它的 require 子命令,比照 jsc-sdlc 轉呼叫 sdlc-gate.sh wp-lock 的慣例。
# 兩邊各拼一份格式就會對不上:2026-08-27 這裡曾自己寫四欄 TSV,而 hooks 那端讀的是
# key=value,狀態檔存在卻解不出欄位。格式只能有一個真實來源。
# 找不到 restart-gate.sh 時不要自己補寫一份:閘門本來就由 jsc-hooks 判讀,它不在就沒有
# 判定點,寫下去只是留一個沒人讀的檔案,還會讓下一輪誤以為閘門掛上了。
mark_restart() {
[ "$DRYRUN" = 1 ] && return 0
case "$MODE" in install|update) ;; *) return 0 ;; esac
if ! rg=$(restart_gate_sh); then
printf 'note\t%s\t%s\n' "$CLI" "找不到 jsc-hooks 的 restart-gate.sh,這次沒有掛上重啟閘門"
return 0
fi
if JSC_CLI="$CLI" sh "$rg" require "$MODE" $DOMAINS </dev/null; then
printf 'restart\t%s\n' "${JSC_HOME:-$HOME/.jsc}/restart-required"
else
printf 'note\t%s\t%s\n' "$CLI" "restart-gate.sh require 失敗,這次沒有掛上重啟閘門"
fi
}
# 執行一個指令,並印出指令本身與結束碼。失敗就記進 FAILED。 # 執行一個指令,並印出指令本身與結束碼。失敗就記進 FAILED。
run() { # $@=指令 run() { # $@=指令
printf 'cmd\t%s\n' "$*" printf 'cmd\t%s\n' "$*"
@@ -284,6 +326,7 @@ case "$CLI" in
esac esac
if [ "$FAILED" -eq 0 ]; then if [ "$FAILED" -eq 0 ]; then
mark_restart
printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS" printf 'result\t%s\t%s\t%s\tok\n' "$CLI" "$MODE" "$DOMAINS"
exit 0 exit 0
fi fi
+264
View File
@@ -0,0 +1,264 @@
#!/usr/bin/env sh
# write-guides.sh — 產生這台機器專屬的更新指引與移除指引。
# 用法:
# write-guides.sh [-n] {install|update} {domain} [domain...]
# -n 或 --dry-run:只印會寫到哪兩個檔案,不寫入。
# {domain} 裸名或帶 jsc- 前綴皆可,交給 deploy.sh 正規化。
# 產出(兩個檔案,整份覆寫):
# $JSC_HOME/update-guide.md 下次更新照著做的指引
# $JSC_HOME/remove-guide.md 要整組移除時照著做的指引
# 輸出(TSV,一行一筆):
# cli<TAB>{cli}<TAB>{path}<TAB>{version} 這台機器上偵測到的 CLI
# plan<TAB>{path} dry-run 時會寫入的檔案
# wrote<TAB>{path} 實際寫入的檔案
# note<TAB>{原因} 非致命的說明(例:一個 CLI 都沒偵測到)
# 結束碼:兩份都寫成 0;參數錯誤 2;目錄或檔案寫不進去 4。
#
# 指引內容一律依實際偵測結果生成,不寫死:
# CLI 清單來自 detect-clis.sh;每支 CLI 的實際指令來自 deploy.sh 的 dry-run(-n)輸出。
# 指令字面因此與 deploy.sh 真正會跑的完全一致——各 CLI 的差異只有 deploy.sh 一個真實來源,
# 這裡再抄一份就會有兩套指令,改了一邊忘了另一邊,指引就開始騙人。
# kiro 走不走本地複製退路,也是讀 deploy.sh 的 note 行判斷,不自己再探測一次。
# 環境變數:
# JSC_HOME 指引寫入的目錄(預設 ~/.jsc)
# GITEA_HOST Gitea 站台,可省略 scheme(預設 https://gitea.jsc.idv.tw)
# JSC_GITEA_OWNER 存取庫的 owner(預設 plugins)
# JSC_LOCAL_PLUGINS antigravity/kiro 用的本地 clone 目錄(預設 $JSC_HOME/plugins)
# JSC_KIRO_SKILLS kiro 退路用的 skills 目錄(預設 ~/.kiro/skills)
set -u
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
DETECT="$HERE/detect-clis.sh"
DEPLOY="$HERE/deploy.sh"
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}"
case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac
HOST="${HOST%/}"
OWNER="${JSC_GITEA_OWNER:-plugins}"
MKT="$HOST/$OWNER/meta.git"
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME/plugins}"
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
UPDATE_GUIDE="$JSC_HOME/update-guide.md"
REMOVE_GUIDE="$JSC_HOME/remove-guide.md"
DRYRUN=0
TAB=$(printf '\t')
usage() {
echo "用法:write-guides.sh [-n] {install|update} {domain} [domain...]" >&2
exit 2
}
case "${1:-}" in
-n|--dry-run) DRYRUN=1; shift ;;
esac
[ $# -ge 2 ] || usage
MODE=$1
shift
case "$MODE" in install|update) ;; *) usage ;; esac
[ -x "$DETECT" ] || [ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 4; }
[ -f "$DEPLOY" ] || { echo "找不到 deploy.sh:$DEPLOY" >&2; exit 4; }
DOMAINS=""
for _d in "$@"; do
case "$_d" in jsc-*) _d="${_d#jsc-}" ;; esac
DOMAINS="$DOMAINS $_d"
done
DOMAINS="${DOMAINS# }"
if [ "$DRYRUN" = 1 ]; then
printf 'plan\t%s\n' "$UPDATE_GUIDE"
printf 'plan\t%s\n' "$REMOVE_GUIDE"
fi
# 偵測到的 CLI,一行一筆 name<TAB>path<TAB>version。
CLIS=$(mktemp) || { echo "無法建立暫存檔" >&2; exit 4; }
NOTES=$(mktemp) || { rm -f "$CLIS"; echo "無法建立暫存檔" >&2; exit 4; }
CMDS=$(mktemp) || { rm -f "$CLIS" "$NOTES"; echo "無法建立暫存檔" >&2; exit 4; }
cleanup() { rm -f "$CLIS" "$NOTES" "$CMDS"; }
sh "$DETECT" > "$CLIS" 2>/dev/null || true
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
printf 'cli\t%s\t%s\t%s\n' "$name" "$path" "$ver"
done < "$CLIS"
[ -s "$CLIS" ] || printf 'note\t%s\n' "一個 CLI 都沒偵測到,指引只會寫下 marketplace 與 domain 清單"
# 跑一次 deploy.sh 的 dry-run,把 cmd 行與 note 行分開收好。
# $1=mode $2=cli;cmd 行寫進 $CMDS,note 行寫進 $NOTES,兩個檔案每次都重寫。
harvest() {
: > "$CMDS"
: > "$NOTES"
JSC_DEPLOY_DRYRUN=1 sh "$DEPLOY" -n "$1" "$2" $DOMAINS 2>/dev/null \
| while IFS="$TAB" read -r kind a b; do
case "$kind" in
cmd) printf '%s\n' "$a" >> "$CMDS" ;;
note) printf '%s\n' "$b" >> "$NOTES" ;;
esac
done
}
# 這支 CLI 的 plugin 安裝方式,一句話。kiro 讀 $NOTES 判斷走不走複製退路。
# codex 分模式講:它的 update 有陷阱(沒有 plugin update),移除段落講這件事只會讓人分心。
cli_method() { # $1=cli $2=mode
case "$1" in
claude|copilot)
printf '原生 plugin 指令,來源是統一 marketplace `jsc`' ;;
codex)
if [ "$2" = uninstall ]; then
printf '原生 plugin 指令;移除用 `plugin remove`,不是 `plugin uninstall`'
else
printf '原生 plugin 指令;沒有 `plugin update` 子指令,更新一律重跑 `plugin add` 就地升級'
fi ;;
antigravity)
printf '不接受 Gitea URL,先把存取庫 clone 到 `%s`,再從本地路徑安裝' "$LOCAL_DIR" ;;
kiro)
if [ -s "$NOTES" ]; then
printf '這個版本沒有 `plugin` 子指令,整批走本地複製退路(複製到 `%s`)' "$KIRO_SKILLS"
else
printf '原生 plugin 指令;單一 domain 失敗才退回本地複製(複製到 `%s`)' "$KIRO_SKILLS"
fi ;;
*)
printf '原生 plugin 指令' ;;
esac
}
# 指令區塊:一支 CLI 一個 sh 圍欄,內容就是 deploy.sh 會跑的每一行。
emit_cmds() { # $1=mode $2=cli
harvest "$1" "$2"
printf '### %s\n\n' "$2"
printf '%s\n\n' "$(cli_method "$2" "$1")"
if [ -s "$NOTES" ]; then
while IFS= read -r n; do
[ -n "$n" ] || continue
printf '> %s\n\n' "$n"
done < "$NOTES"
fi
printf '```sh\n'
if [ -s "$CMDS" ]; then
cat "$CMDS"
else
printf '# deploy.sh 這一輪沒有要對 %s 執行的指令\n' "$2"
fi
printf '```\n\n'
}
domain_list() {
_out=""
for d in $DOMAINS; do
[ -z "$_out" ] && _out="\`jsc-$d\`" || _out="$_out、\`jsc-$d\`"
done
printf '%s' "$_out"
}
domain_count() {
set -- $DOMAINS
printf '%s' "$#"
}
STAMP=$(date '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date)
# 主機與帳號分兩欄寫。併成一欄要用斜線隔開,那是 STE100 的並列斜線違規。
HOSTNAME_NOW=$(hostname 2>/dev/null || echo 未知主機)
USER_NOW=$(id -un 2>/dev/null || echo 未知帳號)
# 兩份指引共用的抬頭:說清楚這份檔案是誰產生的、什麼時候產生的、依據是什麼。
header() { # $1=標題 $2=一句用途
printf '# %s\n\n' "$1"
printf '%s\n\n' "$2"
printf '本檔由 `jsc-cli/tools/write-guides.sh` 產生,內容依產生當下這台機器的偵測結果生成。重跑 `/jsc-cli:deploy` 會整份覆寫。\n\n'
printf '| 項目 | 內容 |\n| --- | --- |\n'
printf '| 產生時間 | %s |\n' "$STAMP"
printf '| 產生時機 | `/jsc-cli:deploy` 的 %s 收尾 |\n' "$MODE"
printf '| 主機 | %s |\n' "$HOSTNAME_NOW"
printf '| 登入帳號 | %s |\n' "$USER_NOW"
printf '| Marketplace | `jsc`(`%s`) |\n' "$MKT"
printf '| 安裝 token | `jsc-{domain}@jsc` |\n'
printf '| Domain 數量 | %s |\n' "$(domain_count)"
printf '| Domain 清單 | %s |\n' "$(domain_list)"
printf '| 資料目錄 | `%s` |\n\n' "$JSC_HOME"
}
cli_table() {
printf '## 這台機器偵測到的 CLI\n\n'
if [ -s "$CLIS" ]; then
printf '| CLI | 執行檔 | 版本 | plugin 安裝方式 |\n| --- | --- | --- | --- |\n'
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
harvest "$MODE" "$name"
printf '| %s | `%s` | %s | %s |\n' "$name" "$path" "${ver:-未知}" "$(cli_method "$name" "$MODE")"
done < "$CLIS"
printf '\n'
else
printf '一個 CLI 都沒偵測到。裝好任一支 CLI 之後重跑 `/jsc-cli:deploy`,這份指引才會有指令可循。\n\n'
fi
}
# 各 CLI 的指令段落。$1=mode
cli_sections() {
[ -s "$CLIS" ] || return 0
while IFS="$TAB" read -r name path ver; do
[ -n "${name:-}" ] || continue
emit_cmds "$1" "$name"
done < "$CLIS"
}
write_update_guide() {
header 'jsc 技能組更新指引' '整組 jsc plugins 要更新時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `update`;下面的指令是同一件事的手動版本,CLI 壞掉或不想開工作階段時用。'
cli_table
printf '## 更新指令\n\n'
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh update {cli} {domain}...` 實際會跑的完全相同。\n\n'
cli_sections update
printf '## 更新完要做的事\n\n'
printf '1. 重新接線 hooks:`/jsc-hooks:hooks-install`。plugin 換版後接線檔會過期。\n'
printf '2. 關閉目前的工作階段並重新啟動。新的技能內容要重開工作階段才載入得到。\n'
printf '3. 體檢一次:`/jsc-cli:doctor`。確認版本、接線與設定都對得上。\n\n'
printf '`%s` 這個檔案存在,就代表有一輪部署還沒重啟。重啟提示由 `jsc-hooks` 判讀,逃生門是 `JSC_RESTART_GATE=off`。\n\n' "$JSC_HOME/restart-required"
printf '## 移除\n\n'
printf '要整組移除看 `%s`。\n' "$REMOVE_GUIDE"
}
write_remove_guide() {
header 'jsc 技能組移除指引' '整組 jsc plugins 要移除時,照這份指引走。首選一律是 `/jsc-cli:deploy` 選 `uninstall`;下面的指令是同一件事的手動版本。'
cli_table
printf '## 移除指令\n\n'
printf '每支 CLI 各自一組,指令與 `jsc-cli/tools/deploy.sh uninstall {cli} {domain}...` 實際會跑的完全相同。marketplace 指令一輪只跑一次,排在各 domain 之後。\n\n'
cli_sections uninstall
printf '## 移除後的殘留物\n\n'
printf 'plugin 指令只管 plugin 自己。下面這些是 jsc 另外寫在機器上的東西,要不要清掉自己決定:\n\n'
printf '| 路徑 | 內容 | 清掉的影響 |\n| --- | --- | --- |\n'
printf '| `%s` | hook 資料目錄:工作階段計時、用量統計、版本快取、模型標籤表、備份 |' "$JSC_HOME"
printf ' 用量統計與設定備份一起消失,救不回來 |\n'
printf '| `%s` | antigravity 與 kiro 用的本地 clone | 下次安裝要重新 clone |\n' "$LOCAL_DIR"
printf '| `%s` | kiro 複製退路的技能目錄 | kiro 的 jsc 技能完全消失 |\n' "$KIRO_SKILLS"
printf '| shell rc 檔的 `# jsc-config` 段落 | `/jsc-cli:setup` 寫入的環境變數 | jsc 相關環境變數失效,段落外的內容不受影響 |\n\n'
printf 'rc 檔那一段用 `jsc-cli/tools/apply-config.sh unset {KEY}` 逐項移除,或直接手動刪掉 `# jsc-config` 與 `# /jsc-config` 之間的內容。動手前先備份。\n\n'
printf '## 移除完要做的事\n\n'
printf '1. 關閉目前的工作階段並重新啟動。已載入的技能要重開工作階段才會消失。\n'
printf '2. 確認殘留:`/jsc-cli:doctor` 若還列得出 jsc 版本,代表某支 CLI 還留著 plugin。\n'
}
[ "$DRYRUN" = 1 ] && { cleanup; exit 0; }
mkdir -p "$JSC_HOME" 2>/dev/null || { cleanup; echo "無法建立目錄:$JSC_HOME" >&2; exit 4; }
rc=0
tmp="$JSC_HOME/.update-guide.md.tmp"
if write_update_guide > "$tmp" 2>/dev/null && mv "$tmp" "$UPDATE_GUIDE" 2>/dev/null; then
printf 'wrote\t%s\n' "$UPDATE_GUIDE"
else
rm -f "$tmp"
echo "無法寫入 $UPDATE_GUIDE" >&2
rc=4
fi
tmp="$JSC_HOME/.remove-guide.md.tmp"
if write_remove_guide > "$tmp" 2>/dev/null && mv "$tmp" "$REMOVE_GUIDE" 2>/dev/null; then
printf 'wrote\t%s\n' "$REMOVE_GUIDE"
else
rm -f "$tmp"
echo "無法寫入 $REMOVE_GUIDE" >&2
rc=4
fi
cleanup
exit "$rc"