Author SHA1 Message Date
admin 2be034c41f Merge pull request 'chore/marketplace-registry-sync/register-assist-entry' (#50) from chore/marketplace-registry-sync/register-assist-entry into chore/marketplace-registry-sync/main
Reviewed-on: #50
2026-09-01 04:39:33 +00:00
jiantw83 233e3a4b41 chore(marketplace): 把 jsc-assist 登錄進統一 marketplace
What:
- 兩份 marketplace 檔各加一個 jsc-assist 條目,來源網址指向 assist 存取庫。

Why:
- 準則要求每個 domain 存取庫都帶同一份 marketplace 檔,任何一個存取庫都能當註冊入口。副本之間只要有一份沒跟上,稽核就會報出不一致。

How:
- 條目由 meta 的 sync-marketplace.sh 產生,同時寫進正本與每個 domain 存取庫的副本,寫完逐檔比對位元組。這一支存取庫的兩份副本就是那一輪的產物。
- 條目依名稱排序,縮排與非 ASCII 描述的處理都交給同一支腳本,不手改 JSON。

Who:
助理 domain 落地的註冊步驟在這個存取庫的同步。
2026-09-01 11:21:31 +08:00
jiantw83 36e1bebe1f Merge pull request '收攏 models 技能的 frontmatter 語法修正' (#48) from feat/cli-hook-rewire/main into develop 2026-09-01 00:58:39 +00:00
jiantw83 749b1ad6d3 Merge pull request '修正 models 技能 SKILL.md frontmatter 的 YAML 純量語法' (#47) from feat/cli-hook-rewire/quote-description into feat/cli-hook-rewire/main 2026-09-01 00:56:08 +00:00
jiantw83 daa4bcf9e1 fix(frontmatter): 修正 models 技能 SKILL.md frontmatter 的 YAML 純量語法錯誤
What:
- 修正 skills/models/SKILL.md frontmatter 裡 description 欄位的 YAML 語法錯誤。
- 整串 description 加上單引號,內部撇號改寫成兩個單引號,內容文字一個字都沒變。
- 同步更新 plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 三個 manifest 版本號,從 0.2.6 進到 0.2.7。

Why:
- description 內含「冒號加空白」,屬於未加引號的 YAML plain scalar,違反 YAML 語法規定。
- Antigravity 解析 frontmatter 時當場中斷,整支技能被靜默丟棄,沒有任何錯誤訊息;磁碟上 34 支技能,Antigravity 只認得 28 支。
- 準則要求 description 用英文撰寫,不能把「: 」改成全形冒號迴避語法問題,只能加引號修正。

How:
- 整串 description 值加上單引號,內部撇號寫成兩個單引號跳脫,其餘字元不動。
- 用 git show HEAD: 取出改前的原始值,把改後的單引號純量還原後做字串相等比對,確認逐字相同、字元數一致。
- 執行 ste100-lint.sh、check-behaviors.sh、lint-frontmatter.sh 三支檢查腳本,退出碼皆為 0;git diff --numstat 顯示只動了 frontmatter 那一行。

Who:
- 本次修到 cli 技能組的 models 技能,屬盤點各 CLI 可用模型與能力標籤的功能。
2026-08-31 19:02:43 +08:00
jiantw83 7d58afa2ee Merge pull request '收攏技能行為清單與相依版本阻擋改動' (#45) from feat/skill-behaviors-and-version-block/main into develop 2026-08-31 08:10:33 +00:00
jiantw83 b5d72f5245 Merge pull request '相依版本不符改為照樣更新並提醒、新增技能行為清單' (#44) from feat/skill-behaviors-and-version-block/deploy-requires-warn into feat/skill-behaviors-and-version-block/main 2026-08-31 08:09:19 +00:00
jiantw83 99e0554995 chore(plugin): 版號升到 0.2.6
What:plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 三份 manifest 的 version 從 0.2.5 改成 0.2.6。

Why:這一輪改了相依檢查的結束碼與部署時的處置,外部行為跟 0.2.5 不同。三份 manifest 是版本檢查與部署推薦的依據,版號不動,version-guard.sh 就看不出這台機器該更新。

How:三份檔案只改 version 一個欄位,其餘內容不動,三份保持同一個版號。

Who:版號發布。
2026-08-31 13:35:43 +08:00
jiantw83 5f1c4cc6d7 docs(deploy): 同步相依檢查改為照樣更新的行為
What:SKILL.md 第 4 步改寫成四種結束碼的處置,第 7 步的回報清單補上 warn 行。README 的 check-requires.sh 表格列與 deploy 技能段落改寫成同一套說法。

Why:文件還寫著版本不符就跳過該 domain。操作者依文件預期那個 domain 不會動,實際上它已經更新,回報也對不上腳本印出來的行。

How:SKILL.md 逐一寫出 0、1、4、2 或其他四種結束碼各自的處置與理由,完成條件改成 skip 要有檢查腳本出錯或本地樹的原因、warn 要指名還缺哪一版。README 兩處改寫成同樣的四種分流,並寫明真正的阻擋在 version-guard.sh。

Who:相依版本不符的處置。
2026-08-31 13:35:42 +08:00
jiantw83 ed4092bb3d fix(deploy): 相依版本不符改成照樣更新並提醒
What:check_requires() 對 check-requires.sh 的四種結束碼重新分流。0 照常更新。1 改印一行 warn,該 domain 照樣更新,訊息寫出還缺哪一版。4 印一行 note,也照樣更新。2 或其他代碼維持印 skip、跳過該 domain,並記成失敗。

Why:跳過會讓落後的 domain 永遠等不到它要的相依版本,也就永遠更新不到,兩個 domain 互相等就形成死鎖。阻擋移到技能叫用那一層,由 jsc-hooks 的 version-guard.sh 執行,更新照跑不會壞事。判不出結論跟版本落後要講不同的話,混成一句會把環境問題誤導成版本問題。檢查腳本自己出錯是另一回事,讀不到結論就不能當成通過。

How:結束碼 1 的分支從印 skip、回傳 1 改成印 warn、回傳 0,結束碼 4 新增一個印 note、回傳 0 的分支,其餘代碼維持原本的 skip 與 FAILED。函式上方與檔頭補上這四條分流的理由。檔頭的輸出格式表補進 warn 與 compat 兩欄,結束碼說明也把 warn 列為不算失敗但要據實回報。

Who:相依版本不符的處置。
2026-08-31 13:35:41 +08:00
jiantw83 4ed0bf11a2 fix(check-requires): 分開相依不符與判不出結論的結束碼
What:把原本的結束碼 1 拆成兩個。1 只代表相依版本不符或缺相依 plugin。新增 4 代表判不出結論,成因是 manifest 不存在、不是有效 JSON、或缺 python3。這三種成因的輸出從 status=blocked 改成 status=unknown。

Why:兩種成因共用同一個結束碼,呼叫端就只能用同一句話講兩件事。環境壞掉會被講成版本落後。操作者照著去補版本,補到最後也碰不到真正的問題點。

How:找不到 manifest、找不到 python3、JSON 解析失敗三處改回傳 4,狀態字串一併改成 unknown。檔頭的輸出格式表與結束碼表跟著改寫,並寫下 1 與 4 分開的理由。

Who:相依檢查結論分流。
2026-08-31 13:35:41 +08:00
jiantw83 76b3f2dcb6 docs(references): 新增五支技能的行為清單作為驗證基準
What:新增 references/behaviors.md。這一頁列出 delegate、deploy、doctor、models、setup 五支技能的行為。每支技能記錄觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象五個項目。

Why:技能驗證以前沒有共同基準。驗證的人只能自己讀 SKILL.md 反推該有哪些行為,兩個人推出來的結果不會一樣。有了這一頁,驗證就比對同一份基準。

How:一支技能一個章節,章節內用一張兩欄表格寫滿五個項目。外部呼叫欄位寫出腳本路徑與子命令,可驗證跡象欄位寫出實際會被改動的檔案或目錄。頁首寫明規則:技能異動時,要在同一個 PR 內一起更新這一頁。

Who:技能驗證基準。
2026-08-31 13:35:40 +08:00
admin 8d17f231cf Merge pull request 'fix/skill-check-compliance-and-flow' (#42) from fix/skill-check-compliance-and-flow into develop
Reviewed-on: #42
2026-08-31 03:19:16 +00:00
jiantw83 f19b9494b4 chore(cli): 補上 jsc-ask 相依宣告並推進版本
delegate 與 setup 都靠決策樹問使用者,deploy 問部署模式也是。這份相依
過去沒有寫進 manifest,安裝順序沒排對,就會執行到一半才失敗。現在三份
manifest 都補上這一項,更新前的相依檢查才擋得住。同時做一次版本推進,
讓已發佈版本對得上這一輪的內容。
2026-08-31 11:09:59 +08:00
jiantw83 8112905364 docs(cli): 把結束碼約定寫進腳本檔頭,README 跟著對齊
detect-clis.sh 與 list-models.sh 一律回零:找不到執行檔,或讀不到設定檔,
都只是少掉那一列,不算失敗。這個約定過去只存在讀過腳本的人腦袋裡。
呼叫端很容易拿結束碼去判斷「這台機器有沒有裝 CLI」,然後永遠判斷錯。
現在把它寫在檔頭,兩支腳本的行為都不動。

README 的腳本表與技能說明同步更新,讀 README 的人看到的流程,才跟技能
裡實際寫的一致。
2026-08-31 11:09:59 +08:00
jiantw83 e30bbf5780 feat(cli): 待修項目合併收成共用腳本,檢查改為併行
「待修項目」表原本由 doctor 與 setup 各寫一次。同一套合併與排序規則寫在
兩個地方,遲早各自漂移:一邊改了排序,另一邊漏掉一整類項目,而且沒有
任何地方看得出來。現在規則只留一份,兩支技能都呼叫它。輸入與輸出都是
TSV,一項都沒有時照樣印一列,呼叫端永遠有東西可以呈現。

doctor 的四項檢查彼此不共用資料,排成一列跑只是把等待時間乘上四倍,
現在同時啟動。doctor 呼叫接線腳本一律帶唯讀旗標,把「打錯一個子命令就
改到或刪掉檔案」的風險移進程式層,不再只靠指令打對。

deploy 的 CLI 偵測、版本結論與 marketplace 清單同樣互不相干,改成併行
取得。版本結論改讀版本守門腳本的單行結論,不再自己從表格推導。setup 把
已經確認過的模式與版本報告直接交給 deploy,操作者不必再答一次同樣的問題。

deploy、doctor、models 都補上結束碼分流:腳本回什麼碼就走哪條路,不再從
輸出內容猜。體檢目錄頁改成先讀回再更新自己那一列,整頁覆蓋會把別台機器
的紀錄一次抹掉。設定規格表補上技能盤點頁要用的環境變數,盤點頁才有地方
可寫。
2026-08-31 11:09:59 +08:00
jiantw83 e8bf4ddaaf fix(cli): 分開「模型不合格」與「腳本被叫錯」的結束碼
model-tags.sh 的 gate 原本用同一個結束碼表示兩件事:模型缺能力標籤,
以及這支腳本被叫錯。delegate 讀到用法錯誤時,會當成模型沒通過檢查,
默默把一個能用的模型丟掉。真正的錯在哪,永遠不會浮出來。現在用法錯誤
改走另一個碼,兩種「無法判定」也歸到同一個碼,呼叫端只要認碼就分得出
三種結果。model-config.sh 照同一套規則調整,兩支腳本的契約才一致。

check-requires.sh 在沒有 python3 的機器上,會直接讓 shell 回一個沒宣告過
的碼,呼叫端讀不到原因。現在先確認 python3 在不在,並印出擋下的理由,
manifest 相依檢查才是真的做得到的事。

delegate 的七個步驟原本沒有任何完成條件,引用了不存在的 plugin,也把
CLI 偵測與標籤篩選寫成文字敘述,可是這兩件事早就有腳本負責。挑模型那
一步需要模型 id,全篇卻沒有任何步驟產得出來。現在每一步都寫出完成條件,
資料一律取自腳本,模型夠不夠格由結束碼判定,不讓模型自評標籤。

setup 的環境變數重驗原本去讀目前這個 shell。可是寫進 rc 檔的值,要等新的
shell 起來才存在,所以重驗永遠回報「沒設到」,把修好的項目誤判成失敗。
現在只驗 rc 段落裡確實有那一行,環境層面交給下一次體檢。
2026-08-31 11:09:59 +08:00
admin 369e6e59f6 Merge pull request 'fix(codex-deploy): 保留 CLI 自身快取相容路徑' (#40) from fix/codex-cli-cache-compat into develop
Reviewed-on: #40
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-28 10:32:38 +00:00
jiantw83 5e4c413dec fix(codex-deploy): 保留 CLI 自身快取相容路徑 2026-08-28 18:31:04 +08:00
admin ca58604afc Merge pull request 'fix(codex-deploy): 保留舊 hooks 快取相容路徑' (#38) from fix/codex-cache-compat into develop
Reviewed-on: #38
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-28 10:24:54 +00:00
jiantw83 90b89047e3 fix(codex-deploy): 保留舊 hooks 快取相容路徑 2026-08-28 18:18:28 +08:00
23 changed files with 675 additions and 300 deletions
+8
View File
@@ -13,6 +13,14 @@
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
+8
View File
@@ -13,6 +13,14 @@
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jsc-cli",
"version": "0.2.3",
"version": "0.2.7",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills",
"author": {
@@ -16,6 +16,7 @@
],
"jsc": {
"requires": {
"jsc-ask": ">=0.0.7",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
+2 -1
View File
@@ -1,10 +1,11 @@
{
"name": "jsc-cli",
"version": "0.2.3",
"version": "0.2.7",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills",
"jsc": {
"requires": {
"jsc-ask": ">=0.0.7",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
+9 -10
View File
@@ -23,16 +23,16 @@ 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/test-clis.sh` | 實際呼叫已偵測到的 AI CLI,跑版本、說明頁與 plugin 清單等唯讀命令(`test-clis.sh [cli...]`);印出每項命令、結束碼、判定與輸出摘要,最後一行 `summary` 標出通過、降級、失敗、略過數。`JSC_CLI_TEST_TIMEOUT` 可調整單項命令逾時秒數,預設 10 秒 |
| `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` 時優先用 `$JSC_HOME/current/jsc-hooks`、本地 clone 與 Kiro skills,最後才掃各 CLI 快取,避免部署收尾綁死單一 CLI 的版號路徑。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
| `tools/check-requires.sh` | `check-requires.sh {cli} {manifest}` 檢查 manifest 的 `jsc.requires` 最低版本。沒有宣告就通過;版本不符或缺相依 plugin 就回 `status=blocked`。`deploy.sh update` 在每個 domain 更新前呼叫它,不符就跳過該 domain 並列出原因 |
| `tools/deploy.sh` | 對單一 CLI 執行安裝、更新或解除安裝(`deploy.sh [-n] {mode} {cli} {domain}...`,mode 為 install / update / uninstall);印出每個指令與其結束碼,最後一行 `result` 標 ok 或 fail。`-n` 只印指令不執行。上表五個 CLI 的指令差異全部收在這支腳本裡。Codex 更新 `jsc-cli` 與 `jsc-hooks` 後會把舊版快取路徑補成指向新版的相容連結,避免正在跑的部署流程找不到 helper 腳本,也避免尚未重啟的工作階段在 Stop hook 階段找不到舊路徑。install 或 update 全數成功時,收尾轉呼叫 `jsc-hooks` 的 `restart-gate.sh require` 掛上重啟閘門,並印一行 `restart` 標出狀態檔位置;uninstall 不寫。尋找 `restart-gate.sh` 時優先用 `$JSC_HOME/current/jsc-hooks`、本地 clone 與 Kiro skills,最後才掃各 CLI 快取,避免部署收尾綁死單一 CLI 的版號路徑。狀態檔的路徑、格式與判讀全在 `restart-gate.sh`,這支腳本不自己拼——格式只留一個真實來源。站台取自 `GITEA_HOST`,本地 clone 目錄取自 `JSC_LOCAL_PLUGINS`,兩者的預設值見下表 |
| `tools/check-requires.sh` | `check-requires.sh {cli} {manifest}` 檢查 manifest 的 `jsc.requires` 最低版本。沒有宣告就通過;版本不符或缺相依 plugin 就回 `status=blocked` 與結束碼 1。`deploy.sh update` 在每個 domain 更新前呼叫它一次:結束碼 1 只印一行 `warn`,那個 domain 照樣更新——跳過會讓落後的 domain 永遠等不到相依版本,也就永遠更新不到,真正的阻擋由 `jsc-hooks` 的 `version-guard.sh` 在技能被叫用時執行;結束碼 2 以上是檢查腳本自己出錯,讀不到結論就不當成通過,印 `skip` 並跳過該 domain |
| `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/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/scan-config.sh` | 依規格表盤點設定現況(`scan {global\|project\|all}` 印 TSV 與 summary、`spec` 印規格表、`orphans` 找出漏登錄的變數);`-o` 為離線模式,需要連 Gitea 的檢查一律標 skipped。唯讀,不寫任何設定;帶 TOKEN 的項目只印 set 或 unset |
| `tools/apply-config.sh` | 把設定寫進 shell rc 檔(`set {KEY} {VALUE}`、`unset {KEY}`)或建立目錄(`mkdir {PATH}`);`show` 印出目前設定,`rcfiles` 印出會寫入的檔案。內容一律收在 `# jsc-config` 標記段落之間,整段重寫不疊加,段落外不動。動檔案前先備份到 `$JSC_HOME/backup/config/{yyyyMMdd_HHmmss}/`,備份失敗就不寫;寫完重讀驗證。fish 自動改用 `set -gx` 語法 |
| `tools/model-tags.sh` | 解析 `references/model-tags.md` 的能力標籤與 SDLC 階段必要標籤(`dump`、`sync`、`stage {階段}`、`model {模型 id}`、`gate {階段} {模型 id}`);`sync` 寫出 `$JSC_HOME/model-tags.tsv` 供 `jsc-hooks` 的 sdlc-gate 讀取 |
| `tools/model-tags.sh` | 解析 `references/model-tags.md` 的能力標籤與 SDLC 階段必要標籤(`dump`、`sync`、`stage {階段}`、`model {模型 id}`、`gate {階段} {模型 id}`);`sync` 寫出 `$JSC_HOME/model-tags.tsv` 供 `jsc-hooks` 的 sdlc-gate 讀取。`gate` 的結束碼 0 為 PASS、1 為缺標籤、2 為模型或階段不在表上,`/jsc-cli:delegate` 依這三碼決定收下或退回 |
| `tools/build-todo.sh` | 把三支檢查腳本的輸出合併成一張「待修項目」表(`--config` 收 `scan-config.sh scan`、`--wiring {cli}=` 收 `wire-cli.sh status`、`--version` 收 `version-guard.sh report`);類別固定排成 missing、invalid、unwired、落後,一項都沒有時仍印一列「無」。`/jsc-cli:doctor` 與 `/jsc-cli:setup` 共用這一份合併規則,兩邊各寫一次就會各自漂移 |
## Skills 目錄
@@ -42,23 +42,23 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
### `models`
用 `tools/list-models.sh` 讀出各已安裝 CLI 可使用的模型並加上能力標籤(`references/model-tags.md`),用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意),並用 `tools/model-config.sh` 列出各階段的偏好模型鏈。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。
**三支腳本併行取得資料**:`tools/detect-clis.sh` 取已安裝 CLI、`tools/list-models.sh` 讀出各 CLI 可使用的模型、`tools/model-config.sh list` 取各階段的偏好模型鏈。接著依 `references/model-tags.md` 加上能力標籤,用 `tools/model-tags.sh sync` 把標籤表寫進 `$JSC_HOME/model-tags.tsv` 供 sdlc-gate 讀取,並列出 SDLC 各階段的必要標籤(plan、analyze 需 `reasoning-max`;implement 需 `coding`;maintain 任意)。`jsc-sdlc` 閘門一律以能力標籤判定,偏好鏈只用來建議切換目標。
### `delegate`
把單一明確任務交給另一個已安裝的 AI agent CLI 當作 subagent 執行。可指定目標 CLI,也可依任務需求用能力標籤篩選模型,或強制指定模型。每個目標分開派工,預設只讀,並回傳結構化結果供主 agent 驗證與彙整。此技能不負責模型盤點或 plugin 部署。
把單一明確任務交給另一個已安裝的 AI agent CLI 當作 subagent 執行。CLI 清單一律取自 `tools/detect-clis.sh`,模型夠不夠格一律由 `tools/model-tags.sh gate` 的結束碼判定,不讓模型自評標籤。可指定目標 CLI,也可依任務需求用能力標籤篩選模型,或強制指定模型。每個目標分開派工,預設只讀,寫入範圍以路徑清單明列;回傳採固定的 TSV 契約(`result`、`summary`、`criterion`、`wrote`、`error`),供主 agent 逐項驗證。此技能不負責模型盤點或 plugin 部署。
### `deploy`
技能庫批次安裝、更新、解除安裝:偵測 CLI → **先比對各 plugin 的本機與已發佈版本並列表,只要有任一個落後就把「更新」設為推薦選項** → 決策樹選模式 → 每個 CLI 一個 sub agent 呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)→ update 前逐一檢查 `jsc.requires`,版本不符就跳過該 domain 並回報缺哪一版 → install、update 後呼叫 `jsc-hooks:hooks-install`,並把每支 CLI 的 smoke 結果納入部署成敗。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。
技能庫批次安裝、更新、解除安裝:**偵測 CLI、版本建議、marketplace domain 清單三者併行取得** → 秀出 `jsc-hooks/hooks/version-guard.sh report` 的版本證據表,再依 `recommend` 的一行結論(`recommend<TAB>{update|none|unverifiable}`,只印這一行,不混印表格)標出推薦選項;輸出裡找不到 `recommend` 行就退回 `report` 自行推導並在回報寫明是降級路徑 → 決策樹選模式(呼叫方已確認過模式就沿用,不重問)→ 每個 CLI 一個 sub agent,**全部同時啟動**呼叫 `tools/deploy.sh` 執行原生 plugin 指令(統一 marketplace `jsc`,token `jsc-{domain}@jsc`)→ update 前逐一檢查 `jsc.requires`,版本不符只印 `warn` 並照樣更新該 domain、回報還缺哪一版,阻擋交給 `version-guard.sh` 在技能被叫用時執行;檢查腳本自己出錯才印 `skip` 跳過該 domain → Codex 更新 `jsc-cli` 與 `jsc-hooks` 時保留舊快取相容連結 → install、update 後把偵測到的 CLI 清單交給 `jsc-hooks:hooks-install`,取它的彙總結果,只自行加判一條「smoke 出現 `No such file` 一律視為更新失敗」。domain 名單動態取自 `plugins/meta` 的 marketplace.json,不硬編碼。
### `doctor`
一次體檢執行環境,只讀不改。五項檢查:技能版本(`jsc-hooks/hooks/version-guard.sh report`)、Hook 接線(`jsc-hooks/tools/wire-cli.sh status`,唯讀子命令)、CLI 實測(`tools/test-clis.sh` 實際呼叫版本、說明頁與 plugin 清單)、全域設定與自我設定(`tools/scan-config.sh` 比對 `tools/config-spec.tsv`)。每項各出一張表,整份結果寫進 wiki `CHECK_{HASH}`,`HASH` 取 `{主機名}/{登入帳號}`,只保留最新一次。修復交給 `/jsc-cli:setup`,體檢本身不動任何設定。
一次體檢執行環境,只讀不改。四項檢查**同時啟動,各一個 sub agent**:技能版本(`jsc-hooks/hooks/version-guard.sh report`)、Hook 接線(`jsc-hooks/tools/wire-cli.sh status`,唯讀子命令)、設定現況(`tools/scan-config.sh scan all` 比對 `tools/config-spec.tsv`,一次掃完全域與專案兩個範圍)、漏登錄變數(`scan-config.sh orphans`)。唯讀契約下放程式層:呼叫 `wire-cli.sh` 一律帶 `JSC_READONLY=1`,打錯子命令也不會改到機器。每項各出一張表,專案那張一定寫出掃的是哪個目錄;待修項目由 `tools/build-todo.sh` 合併三份輸出並排序。整份結果寫進 wiki `CHECK_{HASH}`,`HASH` 取 `{主機名}/{登入帳號}`,只保留最新一次。修復交給 `/jsc-cli:setup`,體檢本身不動任何設定。
### `setup`
修復 `/jsc-cli:doctor` 找出的問題,一次一項,逐項確認才動手。待修清單優先讀 wiki `CHECK_{HASH}`,沒有頁面就當場重掃一份。依修法分流:`auto` 用 `tools/apply-config.sh` 直接寫、`ask` 先用決策樹問到值再寫、`manual` 印出步驟交給操作者。複合修復交回原主:版本落後找 `/jsc-cli:deploy`、hook 未接線找 `/jsc-hooks:hooks-install`、缺 `model-tags.tsv` 找 `/jsc-cli:models`。每一項寫完都重驗一次,最後覆寫 CHECK 頁。
修復 `/jsc-cli:doctor` 找出的問題,一次一項,逐項確認才動手。待修清單優先讀 wiki `CHECK_{HASH}`,沒有頁面就當場重掃:**三支檢查腳本併行跑**,再用 `tools/build-todo.sh` 合併成同一張表。依修法分流:`auto` 用 `tools/apply-config.sh` 直接寫、`ask` 先用決策樹問到值再寫、`manual` 印出步驟交給操作者。複合修復交回原主:版本落後找 `/jsc-cli:deploy`(連同已確認的模式與版本報告一起傳過去,不讓它重問重查)、hook 未接線找 `/jsc-hooks:hooks-install`、缺 `model-tags.tsv` 找 `/jsc-cli:models`。逐項確認維持循序,**寫完的重驗併行**;環境變數類只驗「rc 段落裡確實有那一行」,環境層面交給下一次 `/jsc-cli:doctor`。最後覆寫 CHECK 頁。
<!-- JSC-SKILLS:END -->
@@ -73,7 +73,6 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安
| `JSC_DEPLOY_DRYRUN` | 設為 `1` 等同 `deploy.sh -n`,只印指令不執行 | 照常執行 |
| `JSC_WIKI_REPO_CHECK` | 體檢頁 `CHECK_CONTENTS`、`CHECK_{HASH}` 所在的 `{owner}/{repo}` | 退回 `JSC_WIKI_REPO`;兩個都沒有就略過寫入,並把這一項列進待修 |
| `JSC_CONFIG_SPEC` | 改讀別份設定規格表(測試 `scan-config.sh` 時用) | 用 `tools/config-spec.tsv` |
| `JSC_CLI_TEST_TIMEOUT` | `test-clis.sh` 單項 CLI 實測命令的逾時秒數 | 用 `10` |
| `JSC_HOME` | hook 資料目錄,兩份指引與重啟狀態檔都寫在這裡 | 用 `~/.jsc` |
| `JSC_RESTART_GATE` | 設成 `off` 可略過部署後的重啟提示閘門(判讀在 `jsc-hooks`,`jsc-cli` 只負責寫狀態檔) | 照常提示重啟 |
+2 -1
View File
@@ -1,10 +1,11 @@
{
"name": "jsc-cli",
"version": "0.2.3",
"version": "0.2.7",
"description": "CLI 偵測、模型能力標籤、子代理派工與技能庫批次部署",
"skills": "./skills/",
"jsc": {
"requires": {
"jsc-ask": ">=0.0.7",
"jsc-gitea": ">=0.1.7",
"jsc-hooks": ">=0.2.8"
}
+53
View File
@@ -0,0 +1,53 @@
# jsc-cli 技能行為清單
本頁記錄 jsc-cli 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
## delegate
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 一件邊界清楚的工作要交給另一支已安裝的 AI agent CLI 執行時用。盤點模型(models)、部署技能組(deploy)、必須留在目前 agent 手上的工作都不用;給不出通過或失敗判準的目標也不用 |
| 關鍵步驟 | 寫下一句目標與可判定通過或失敗的驗收條件、同時起跑 detect-clis.sh 與 list-models.sh 取得 CLI 清單與模型清單、先選定目標 CLI 再用 model-tags.sh 的 gate 或 model 驗證模型能力標籤、組出帶目標、驗收條件、目標 CLI、模型代號、最小脈絡、寫入範圍與 TSV 輸出合約的提示、開一個 sub agent 執行、查驗回傳的結束碼與 result、summary、criterion、wrote 各行、把成功、失敗、需要使用者補充分三段回報 |
| 外部呼叫 | jsc-cli/tools/detect-clis.sh、jsc-cli/tools/list-models.sh、jsc-cli/tools/model-tags.sh(gate 與 model 兩個子命令)、一個代跑工作的 sub agent |
| 完成條件 | 目標 CLI 與模型各只有一個,且模型通過能力要求;sub agent 回傳的每一條驗收條件都有判定;每個 wrote 路徑都落在寫入範圍內;報告分三段列出結果。中途停下時,停下的原因要寫在報告裡 |
| 可驗證跡象 | 技能自己不寫檔。可檢查的是 sub agent 依寫入範圍實際改動的檔案,逐條列在回傳的 wrote 行上,照那些路徑去看即可比對;寫入範圍為空的唯讀委派沒有寫入跡象,只有回報內容 |
## deploy
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 整組 jsc 技能要在這台機器的每一支已安裝 CLI 上安裝、更新或解除安裝時用。只處理單一技能不用;只想知道版本落後與否,看 doctor 就夠 |
| 關鍵步驟 | 同時取得三項事實(detect-clis.sh 的 CLI 清單、version-guard.sh 的 report 版本表與 recommend 結論、marketplace.json 的 domain 清單)、把版本表原樣秀出並定出建議、依 jsc-ask 決策樹問出模式(呼叫端已帶模式就沿用並標明來源)、每支 CLI 各開一個 sub agent 同時跑 tools/deploy.sh {mode} {cli} {domain}...、安裝或更新後把 CLI 清單交給 jsc-hooks:hooks-install、整台機器跑一次 tools/write-guides.sh、彙整每支 CLI 的結果並要求重新啟動工作階段 |
| 外部呼叫 | jsc-cli/tools/detect-clis.sh、jsc-cli/tools/deploy.sh、jsc-cli/tools/write-guides.sh、jsc-cli/tools/check-requires.sh(由 deploy.sh 在每個 domain 更新前轉呼叫)、jsc-hooks/hooks/version-guard.sh 的 report 與 recommend、jsc-hooks/hooks/restart-gate.sh require(由 deploy.sh 收尾轉呼叫)、jsc-gitea/tools/gitea.sh 讀 plugins/meta 的 marketplace.json、jsc-ask:ask、jsc-hooks:hooks-install |
| 完成條件 | 每一支偵測到的 CLI 都回報結束碼與 result 行,每個 skip、warn、compat 行都照實列出;安裝或更新還要拿到 hooks-install 對每支 CLI 的總結,兩份指引都印出 wrote,收尾印出重啟指示與兩份指引路徑 |
| 可驗證跡象 | 各 CLI 的外掛目錄多出或少掉 jsc-{domain}:claude 與 codex 在各自的 plugin 快取、copilot 在 installed-plugins、antigravity 與 kiro 走 $JSC_LOCAL_PLUGINS 的本地 clone 與 $JSC_KIRO_SKILLS 的複製。$JSC_HOME/restart-required.d/{cli} 出現這次的重啟狀態檔;$JSC_HOME/update-guide.md 與 $JSC_HOME/remove-guide.md 被重寫;各 CLI 的 hook 設定檔由 hooks-install 改寫 |
## doctor
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 裝完或更新完技能組、技能因設定或接線問題失敗、機器要交接前用。要動手修不用這支,那是 jsc-cli:setup |
| 關鍵步驟 | 同時開四個 sub agent 收版本、hook 接線、設定與未登錄變數,每個 sub agent 回傳原始輸出行、把四份輸出各存成檔、依 templates/check-page.md 印出五個區塊並寫明掃描的專案目錄、用 tools/build-todo.sh 把三份輸出合成待修項目表、透過 jsc-gitea:wiki 整頁覆寫 CHECK_{HASH} 並把本機那一列 upsert 進 CHECK_CONTENTS、報出四項計數並視情況建議 /jsc-cli:setup |
| 外部呼叫 | jsc-hooks/hooks/version-guard.sh report、jsc-cli/tools/detect-clis.sh、jsc-hooks/tools/wire-cli.sh status(一律帶 JSC_READONLY=1)、jsc-cli/tools/scan-config.sh 的 scan all 與 orphans、jsc-cli/tools/build-todo.sh、jsc-gitea/tools/gitea.sh 的 wiki-repo 與 hash-id、jsc-gitea:wiki |
| 完成條件 | 四項檢查各有結論,或明寫無法驗證與原因;五個區塊與待修項目表都在畫面上;wiki 頁寫成功,或寫入略過連同結束碼一起回報;必要項缺漏、設定錯誤、CLI 未接線、domain 落後四項計數都講出來 |
| 可驗證跡象 | wiki 的 CHECK_{HASH} 頁(雜湊來源是 {主機名}/{登入帳號})被整頁覆寫成這次的結果,CHECK_CONTENTS 多出本機那一列,或該列的缺漏數與最後體檢時間被更新。機器本身的設定、接線與版本都不動:這支技能不寫任何設定 |
## models
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 要盤點各 CLI 可用模型、確認模型合不合 SDLC 階段的能力要求、或檢視階段閘門設定時用。切換模型不用,改 .jsc/models 與 models.conf 也不用 |
| 關鍵步驟 | 同時起跑三個收集器(detect-clis.sh、list-models.sh 以 sub agent 執行、model-config.sh list)、對讀不到設定的 CLI 補上標「預設推定」的預設模型、依 references/model-tags.md 為每個模型掛能力標籤、印出 CLI、模型、標籤、使用中四欄表、跑 tools/model-tags.sh sync 把標籤表寫進 $JSC_HOME/model-tags.tsv、附上 SDLC 階段需求表與階段偏好模型表 |
| 外部呼叫 | jsc-cli/tools/detect-clis.sh、jsc-cli/tools/list-models.sh、jsc-cli/tools/model-config.sh list、jsc-cli/tools/model-tags.sh sync、references/model-tags.md;標籤表上查不到的模型改用 jsc-ask:ask 發問 |
| 完成條件 | 每支偵測到的 CLI 都有模型清單或一組預設推定;每個模型都掛到標籤,或已排進發問;sync 印出寫入路徑,失敗則連同結束碼回報;兩張階段表都列滿 plan、analyze、implement、maintain 四個階段 |
| 可驗證跡象 | $JSC_HOME/model-tags.tsv 被重寫,內容就是這次掛好的標籤表;jsc-hooks/hooks/sdlc-gate.sh 讀的正是這份檔,檔案不在,SDLC 階段閘門就判不出來。各 CLI 的模型設定檔不動 |
## setup
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | doctor 報出待修項目、要實際動手修這台機器時用。只想做唯讀體檢不用這支,那是 jsc-cli:doctor |
| 關鍵步驟 | 從 wiki CHECK_{HASH} 讀待修項目表,讀不到就以 sub agent 同時重跑設定、接線、版本三個檢查器再用 build-todo.sh 合併、依 jsc-ask 決策樹逐項循序確認、依 fix 欄分流(auto 與 ask 走 apply-config.sh 的 set 或 mkdir、manual 印出步驟交給操作者、domain 落後轉呼叫 jsc-cli:deploy 並附上手上的版本報告、hook 未接線轉呼叫 jsc-hooks:hooks-install、缺 model-tags.tsv 轉呼叫 jsc-cli:models)、同時重驗每個已套用項目、重寫 CHECK_{HASH} 並 upsert CHECK_CONTENTS、報出已修、略過、轉呼叫、未修好四項計數 |
| 外部呼叫 | jsc-cli/tools/scan-config.sh、jsc-cli/tools/detect-clis.sh、jsc-hooks/tools/wire-cli.sh status(一律帶 JSC_READONLY=1)、jsc-hooks/hooks/version-guard.sh report、jsc-cli/tools/build-todo.sh、jsc-cli/tools/apply-config.sh 的 set、mkdir 與 show、jsc-gitea/tools/gitea.sh 的 wiki-repo 與 hash-id、jsc-ask:ask、jsc-gitea:wiki、jsc-cli:deploy、jsc-hooks:hooks-install、jsc-cli:models |
| 完成條件 | 每一項都有已修、略過、轉呼叫或未修好的結果;每個已套用項目都由自己那一列指定的檢查器重驗過;每個寫進去的環境變數都附上 export 那一行;頁面寫好或略過都有回報;四項計數都講出來 |
| 可驗證跡象 | 各 shell rc 檔的 `# jsc-config` 區塊被改寫,改寫前的備份落在 $JSC_HOME/backup/config/{時間戳}/;auto 路線建立的目錄實際出現在磁碟上;wiki CHECK_{HASH} 被改寫成修完後的狀態,CHECK_CONTENTS 的本機列跟著更新;轉呼叫出去的項目留下各自技能的跡象,也就是 deploy 的重啟狀態檔、hooks-install 改寫的接線設定、models 產生的 model-tags.tsv |
+90 -22
View File
@@ -7,31 +7,99 @@ description: Delegate a single bounded task to another installed AI agent CLI as
Use this skill when the work should move to another installed AI agent CLI instead of staying in the current agent.
Detection and tag filtering are decided in code, not in prose: `jsc-cli/tools/detect-clis.sh` owns the CLI list and `jsc-cli/tools/model-tags.sh` owns the capability table. This skill only says when to call them and what each exit code means.
## Steps
1. Split the request into one bounded goal. One goal means one subagent.
2. Detect the installed AI agent CLIs. Use only a target CLI that is actually present.
3. Choose the execution model.
- If the user names a CLI, keep only that CLI.
- If the user gives capability tags, keep only models that satisfy all tags.
- If the user forces a model, use that exact model and fail if it does not satisfy the tags.
4. Build the subagent prompt.
- Include the goal, the acceptance criteria, the target CLI, the model choice, and the minimum needed context.
- Include `/jsc-shared:spec-output`.
- State the write scope clearly. If no write scope is granted, say the subagent is read-only.
5. Spawn one subagent for the goal. Do not mix unrelated goals in the same subagent.
6. Verify the result before you report it.
- Check the exit status.
- Check that the return value is structured.
- Check that the result covers the acceptance criteria.
- Check that any writes stayed inside the allowed scope.
7. Report the verified result.
- State the CLI, the model, the capability tags, and the outcome.
- Separate success, failure, and anything that still needs user input.
1. Bound the goal. Write one sentence for the goal, and a numbered list of acceptance criteria that a reader can mark pass or fail without asking a follow-up question.
Two or more independent goals → split them and run this skill once per goal. A goal is not bounded enough when it has no acceptance criterion that can be checked from the subagent's returned text alone.
Done when exactly one goal sentence and at least one pass-or-fail acceptance criterion are written down.
2. Collect the CLI list and the model list. They read different files and share no state, so **start both at once and wait for both**. Step 3 has no other source for a model id: without the second script there is nothing to hand to `model-tags.sh gate`.
`jsc-cli/tools/detect-clis.sh` prints `{name}<TAB>{path}<TAB>{version}` per installed CLI.
| Result | Action |
| --- | --- |
| exit 0, at least one row | Take the candidate CLIs from those rows only |
| exit 0, no row | Stop. Report that no AI agent CLI is installed, and name the five it probes: claude, codex, copilot, antigravity, kiro |
| any non-zero exit | Stop. Report the exit code and the stderr text. Never fall back to a guessed CLI list |
`jsc-cli/tools/list-models.sh` prints `{cli}<TAB>{model}<TAB>{in-use}` per model, read from each CLI's own config, and always exits 0. It stays silent for a CLI whose config it cannot read, so a CLI with no row is a CLI with no known model, not a failure.
| Result | Action |
| --- | --- |
| exit 0 | Take the candidate models for the target CLI from that CLI's own rows, the `in-use` row first |
| any non-zero exit | The script itself failed. Stop and report the exit code and the stderr text; never invent a model id |
Done when the candidate CLI list comes from the first TSV and holds at least one name, the model rows from the second are in hand, or the skill has stopped with the reason.
3. Pick the target CLI, then resolve its model against the requirement. Both read step 2's two TSVs and nothing else, so they belong to one pass over that data.
**Pick the target CLI first.** The user naming a CLI keeps only that CLI; a name absent from step 2's TSV stops the skill with the message that the CLI is not installed on this machine. No name from the user → keep every detected CLI as a candidate, and settle on one before any model is checked: the candidate model list is defined by the target CLI.
**Then resolve the model.** Never let a model judge its own tags — the verdict comes from the script's exit code.
The candidate models are the step 2 `list-models.sh` rows whose first column is the chosen target CLI, checked one at a time in that order, `in-use` first. A user-forced model replaces that list with itself alone. No row for the target CLI and no forced model → stop, and say the fix is to record a model for that CLI through `/jsc-cli:models`; a guessed model id would be gated against a table entry that has nothing to do with the CLI that will actually run the task.
Requirement stated as an SDLC stage → run `jsc-cli/tools/model-tags.sh gate {stage} {model-id}` per candidate. Exit 2 covers three different faults, so read the output line to tell them apart:
| Exit | Output | Action |
| --- | --- | --- |
| 0 | `PASS` | Keep the model and stop checking further candidates |
| 1 | `FAIL:{tags}` | Drop that model, name the missing tags in the report, and move to the next candidate |
| 2 | `UNKNOWN-MODEL` | Drop that model and move to the next candidate. Do not guess its tags. Say the fix is to add it through `/jsc-cli:models` |
| 2 | `UNKNOWN-STAGE` | Stop. The stage name is wrong; name the four valid ones: plan, analyze, implement, maintain |
| 2 | usage text on stderr, no verdict line | Stop. The call itself is malformed — report it as a defect in this skill, and never read it as a model that failed the requirement |
| other | — | Stop. Report the exit code and the stderr text |
Every candidate exhausted without a `PASS` → stop, and report each candidate with its own verdict.
Requirement stated as raw capability tags → run `jsc-cli/tools/model-tags.sh model {model-id}`, which always exits 0 and prints the model's tags, or prints nothing when the model is not on the table. Keep the model only when its tags cover every required tag. Empty output gets the same treatment as `UNKNOWN-MODEL` above.
A forced model goes through the same check. Failing it stops the delegation rather than downgrading the requirement.
Done when exactly one target CLI is chosen and one model id from that CLI has cleared the requirement — a `PASS` from `gate`, or tags covering every required tag — or the skill has stopped with the reason.
4. Build the subagent prompt. It carries the goal, the acceptance criteria from step 1, the target CLI, the model id, the minimum context needed, the write scope, and the output contract below.
The write scope is a list of paths the subagent may write, one per line, each an absolute path or a path relative to the current working directory. An empty list means read-only, and the prompt says so in those words. Anything outside the list is out of scope, including temporary files.
The output contract is a TSV block, and the prompt states it verbatim:
| Line | Meaning |
| --- | --- |
| `result<TAB>{ok\|fail\|needs-input}` | Exactly one, and the last line |
| `summary<TAB>{one line}` | Exactly one |
| `criterion<TAB>{n}<TAB>{pass\|fail}<TAB>{evidence}` | One per acceptance criterion from step 1 |
| `wrote<TAB>{path}` | One per written path, zero when read-only |
| `error<TAB>{message}` | One per failure, zero on success |
Done when the prompt holds all seven parts and the write scope is either a path list or the read-only sentence.
5. Spawn one subagent for the goal, targeted at the CLI and the model from step 3. One goal, one subagent. Done when the subagent has returned and its exit status has been captured.
6. Verify the returned result before reporting it. Every check below must pass:
| Check | Failure handling |
| --- | --- |
| Exit status is 0 | Non-zero → record a failure carrying the target CLI, the exit code and the stderr text |
| Exit status was obtained at all | Not obtainable → treat it as a failure, exactly as a non-zero exit |
| Output holds one `result` line and one `summary` line | Missing either → record a failure reading 回傳格式不符 |
| One `criterion` line per acceptance criterion, all `pass` | Any `fail` or missing line → report the outcome as failure, naming the criteria |
| Every `wrote` path is inside the step 4 write scope | Any path outside → report it as an out-of-scope write and name the path |
Done when every row above has a verdict, and a failing row has produced a recorded failure.
7. Report the verified result: the target CLI, the model id, the capability requirement and how it was met, and the outcome. Keep success, failure and 需要使用者補充 in three separate sections, so a partial result is never read as a finished one.
Done when the CLI, the model, the requirement and the per-criterion verdicts are all in the report, and every failure recorded in step 6 appears in the failure section.
## Do not use this skill
- Do not use it for model inventory.
- Do not use it for plugin deployment.
- Do not use it for model inventory. That is `/jsc-cli:models`.
- Do not use it for plugin deployment. That is `/jsc-cli:deploy`.
- Do not use it for tasks that must stay inside the current agent.
- Do not use it when the task is not bounded enough to hand off cleanly.
- Do not use it for a goal that step 1 could not give a pass-or-fail acceptance criterion.
+80 -19
View File
@@ -1,28 +1,89 @@
---
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. 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.
description: Batch install, update, or uninstall the whole jsc skill set on every installed AI CLI. Detect CLIs, read the version recommendation and the marketplace domain list in parallel, then ask the user for the mode via decision tree unless the caller already passed one, then run each CLI's native plugin commands in parallel with the unified jsc marketplace (token jsc-{domain}@jsc). Domain list comes from the plugins/meta marketplace.json, never hardcoded. After install or update, hand the detected CLI list to jsc-hooks:hooks-install, 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
## Inputs a caller may pass
`jsc-cli:setup` already confirmed the mode with the user and already holds a fresh version report. Re-asking and re-querying would put a second decision tree in front of someone who just answered it.
| Input | Effect |
| --- | --- |
| mode (`install` / `update` / `uninstall`) | Step 3 skips the question and states which caller set the mode |
| version report | Step 1 skips its version collector; step 2 shows the report it was handed and names its source |
Nothing passed in → run every step as written below.
## Steps
1. Run `tools/detect-clis.sh` to find the installed CLIs and their executable paths. Done when the TSV lists at least one CLI with an executable path.
2. **Check versions before asking anything**, so the recommendation is based on fact rather than a guess:
1. Run `jsc-hooks/hooks/version-guard.sh report`. It prints one line per installed jsc plugin — `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` — and a final `behind<TAB>{落後個數}`.
2. **No local plugin registry → report this CLI as unverifiable, never as up to date.** Two forms of the same fact: a report carrying no `{domain}` row at all before the `behind` line, or the script's explicit no-registry line (`noregistry<TAB>{路徑}`). Both mean the version check could not run for this CLI, so `behind<TAB>0` here proves nothing. State that plainly and base no recommendation on it.
3. Show that table to the user as-is. It is the evidence behind the recommendation, so never summarise it away.
4. **`behind` ≥ 1 → mark `update` as the recommended option**, and name every domain that is behind together with its local and remote version. One domain behind is enough; do not wait for a majority.
5. `behind` = 0 **with at least one domain row** → recommend nothing; present the three options neutrally.
6. `查詢失敗` on any domain → say so explicitly. An unverified domain is not the same as an up-to-date one, and must not be counted as either.
1. Collect the three facts the rest of the run needs. They are independent, so start all three at once and wait for all three.
Done when the report is shown and either every domain row carries one of the four status literals `落後` `最新` `超前` `查詢失敗`, or the CLI is reported as having no local registry and therefore unverifiable.
3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. Done when the user has named exactly one of `install`, `update` or `uninstall`.
4. Get the domain list (**never hardcode it**; this skill follows automatically when domains are added or removed): read `plugins[].name` from the unified marketplace via
`jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`.
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, one `requires` line before each domain update, and one `result` line at the end; `-n` prints the commands without running them. On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. A missing or too-old required jsc plugin prints a `skip` line and leaves that domain untouched. 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, and every skipped domain has a dependency reason or local-tree reason.
6. After install or update, call `jsc-hooks:hooks-install` to rewire the hooks. The hook installer must refresh `$JSC_HOME/current/jsc-hooks` and must run `tools/wire-cli.sh smoke {cli}` for every detected CLI. Treat any `No such file` in those smoke results as a failed update and report it; do not let the deploy finish as successful when a rewritten hook path cannot execute. Done when hooks-install reports purge, wiring, smoke and scan results for each detected CLI, and every smoke result is either `status=ok` or explicitly reported as the update failure.
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.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. 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.
1. **Installed CLIs** — `tools/detect-clis.sh`, printing `{name}<TAB>{path}<TAB>{version}`. Exit 0 with at least one row → take the CLI list from it. Exit 0 with no row → stop, and report that none of claude, codex, copilot, antigravity, kiro is installed. Any non-zero exit → stop and report the exit code and stderr; never guess a CLI list.
2. **Version evidence and recommendation** — two subcommands of `jsc-hooks/hooks/version-guard.sh`, both needed, run together: `report` prints the per-plugin rows `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` closing with `behind<TAB>{count}`, and `recommend` prints one single line and nothing else — `recommend<TAB>{update|none|unverifiable}`. `recommend` deliberately never reprints the table, so its second column stays readable by `cut`; the version table that steps 2, 3 and 7 show comes from `report`, and the conclusion comes from `recommend`. Skip this collector when the caller passed a version report.
3. **Domain list** — read `plugins[].name` from the unified marketplace (**never hardcode it**; this skill then follows automatically when domains are added or removed):
`jsc-gitea/tools/gitea.sh api GET /repos/plugins/meta/raw/.claude-plugin/marketplace.json`.
Exit 0 with at least one `plugins[].name` → use that list. Exit 0 with an empty or unparseable list → stop and report that the marketplace holds no plugin entry. Any non-zero exit → stop and report the exit code and stderr; a partial domain list would install a partial skill set and look successful.
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 CLI list holds at least one CLI, the domain list holds at least one name, and the recommendation is either in hand or explicitly inherited from the caller.
2. Show the `report` version table to the user as-is. It is the evidence behind the recommendation, so never summarise it away. A report handed in by a caller is shown the same way, with a line naming that caller as its source.
Branch on the `recommend` line:
| Exit | `recommend` value | What it means and what to say |
| --- | --- | --- |
| 0 | `update` | At least one domain is behind. Mark `update` as the recommended option in step 3, and name every behind domain with its local and remote version. One domain behind is enough; do not wait for a majority |
| 0 | `none` | Every domain row is `最新` or `超前`. Recommend nothing; present the three options neutrally |
| 0 | `unverifiable` | The version check could not run — no local plugin registry, or every remote lookup failed. Say so plainly and base no recommendation on it. Unverified is not the same as up to date |
| 0 | no `recommend` line in the output at all | This jsc-hooks build has no `recommend` subcommand — an older `version-guard.sh` treats the argument as a hook invocation and exits 0 without printing anything. Derive the same three values yourself from the `report` table already collected in step 1.2: any `落後` row → `update`; no `{domain}` row at all, or a `noregistry` line → `unverifiable`; otherwise `none`. Say in step 7's report that the recommendation came from this fallback path, not from `recommend` |
| non-zero | — | Report the version check as `unverifiable` with the exit code and stderr, and carry on to step 3 without a recommendation |
Any single domain row reading `查詢失敗` is called out by name even when the overall value is `none`. An unverified domain is not the same as an up-to-date one, and must not be counted as either.
Done when the table is on screen and the recommendation is stated as exactly one of `update`, `none` or `unverifiable`.
3. Ask the user for the mode per the `jsc-ask:ask` rules: `install` / `update` / `uninstall`. Every option states its impact scope: which CLIs it touches and which configs it writes. A caller that already passed a mode skips this step, and the report names that caller instead.
Done when the user has named exactly one of `install`, `update` or `uninstall`, or the inherited mode is named with its source.
4. 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, and **all of them start together** — the CLIs write to separate plugin directories, so serialising them only adds up their install times.
The script prints `cmd` and `exit` lines for every command, one `requires` line before each domain update, optional `compat` lines for Codex cache links, and one `result` line at the end; `-n` prints the commands without running them.
| Exit | Action |
| --- | --- |
| 0 | Every command for that CLI succeeded. Record its `result` line |
| 1 | At least one command failed. Record that CLI as failed and quote every `exit` line whose code is non-zero |
| 2 | Usage error — the mode, the CLI name or the domain list is wrong. Report it as a defect in this skill, and do not retry with a guessed argument |
| other | Record that CLI as failed with the exit code and stderr |
On update, `tools/check-requires.sh {cli} {manifest}` checks each domain's `jsc.requires` before that domain is updated. Exit 0 updates the domain as usual. Exit 1 — a missing or too-old required jsc plugin — prints a `warn` line and the domain **is still updated**: skipping it would leave a behind domain permanently unable to reach the version its dependency needs. The block lives one layer up, at skill invocation time, where `jsc-hooks/hooks/version-guard.sh` stops that domain's skills. Exit 4 — the manifest is unreadable, is not valid JSON, or python3 is missing — prints a `note` line and also still updates the domain: no verdict is not the same fact as behind, so it gets its own line rather than a `warn` that would send the operator hunting for a version problem that is not there. Exit 2 or any other code — a `check-requires.sh` usage error or a broken script — prints a `skip` line and leaves that domain untouched, because a checker that failed outright is not a pass. Codex update preserves old `jsc-cli` and `jsc-hooks` cache version paths as symlinks to the newest installed version, so a still-running Codex deploy can keep using its helper scripts and a still-running Codex session whose hook_run_id points at the old cache can finish without `No such file`. 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 code and a `result` line, every skipped domain has a checker-failure reason or a local-tree reason, and every `warn` domain is named with the version it still has to catch up to.
5. After install or update, call `jsc-hooks:hooks-install` and **hand it the CLI list from step 1.1**, so it does not probe the same five executables a second time. `hooks-install` still detects for itself when it receives no list — that fallback is what keeps it usable on its own.
Take its aggregate result rather than re-reading each CLI's smoke detail; the installer already judged purge, wiring, smoke and scan per CLI, and refreshes `$JSC_HOME/current/jsc-hooks` on the way — the path `deploy.sh` follows to reach `restart-gate.sh`. Keep exactly one extra judgement here, because it is a deploy-side fact the installer does not rule on: **a smoke result containing `No such file` is a failed update**, since it means a rewritten hook path cannot execute. Report it and do not let the deploy finish as successful.
Done when hooks-install has returned an aggregate verdict for every CLI in the list, and every `No such file` in it is reported as an update failure.
6. After install or update, run `tools/write-guides.sh {mode} {domain}...` **once for the whole machine**, after every CLI in step 4 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.
| Exit | Action |
| --- | --- |
| 0 | Both `wrote` lines printed. Name both paths in step 7 |
| 2 | Usage error — the mode or the domain list is wrong. Report it as a defect in this skill; the deploy itself still stands |
| 4 | `$JSC_HOME` or one of the two files could not be written. Name the path and the stderr, and say the machine has no up-to-date guide until this is fixed |
| other | Report the guide write as failed with the exit code, and say which of the two files did print a `wrote` line |
Done when both `wrote` lines are printed, or the failure is reported with the exit code and the paths involved.
7. Report the run and close it, in one block. The result and any failure reason for every CLI × mode, plus every `skip` line, every `warn` line, every Codex `compat` line, and every CLI that could not be version-checked in step 2.
For install or update, the same block ends with the restart instruction, in these words: 「請關閉目前的工作階段並重新啟動,新的技能內容才會載入」. `deploy.sh` recorded this round in `$JSC_HOME/restart-required.d/{cli}` — one file per CLI — and prints its path on a `restart` line; `jsc-hooks` reads only that CLI's own file and keeps reminding until that CLI restarts, with `JSC_RESTART_GATE=off` as the escape hatch. Restarting one CLI clears its own file and leaves the others' gates standing. Name the two guide paths from step 6 in that same closing block, so the operator knows where this machine's update and removal commands now live.
Done when every detected CLI appears in the report with its `result` status, and — for install or update — the restart instruction is printed with both guide paths named, or step 6's failure is repeated in their place.
+74 -45
View File
@@ -1,90 +1,119 @@
---
name: doctor
description: Health-check the execution environment in one pass and record the result, changing nothing. Five checks - plugin versions from jsc-hooks/hooks/version-guard.sh report, hook wiring from jsc-hooks/tools/wire-cli.sh status, executable CLI tests from tools/test-clis.sh, global settings and current-directory settings from tools/scan-config.sh against tools/config-spec.tsv. Report one findings table per check, then write the whole run to wiki CHECK_{HASH} where HASH comes from {hostname}/{user}; the page keeps only the latest run. Use after installing or updating the skill set, when a skill fails on a settings, wiring or CLI runtime problem, or before handing a machine over; not for applying fixes, which is jsc-cli:setup.
description: Health-check the execution environment in one pass and record the result, changing nothing. Four checks - plugin versions from jsc-hooks/hooks/version-guard.sh report, hook wiring from jsc-hooks/tools/wire-cli.sh status, settings from tools/scan-config.sh against tools/config-spec.tsv, and orphan variables. Report one findings table per check, build the 待修項目 table with tools/build-todo.sh, then write the whole run to wiki CHECK_{HASH} where HASH comes from {hostname}/{user}; the page keeps only the latest run. Use after installing or updating the skill set, when a skill fails on a settings or wiring problem, or before handing a machine over; not for applying fixes, which is jsc-cli:setup.
---
# doctor - execution environment health check
# doctor — execution environment health check
Read-only. Every command below either reads local state, calls a read-only CLI command, or asks Gitea. None of them writes a setting. That is the contract with `jsc-cli:setup`: doctor states the facts, setup changes things.
Read-only. Every command below either reads a file or asks Gitea; none of them writes a setting. That is the contract with `jsc-cli:setup`: doctor states the facts, setup changes things.
Collection (steps 1 to 5) **MUST run as a sub agent** - one sub agent for all five, returning the raw TSV lines. Only the report and the wiki write stay in the main agent.
The read-only contract is enforced in code, not by prose: every `jsc-hooks/tools/wire-cli.sh` call in this skill runs with `JSC_READONLY=1` in its environment. A mistyped subcommand then refuses to write instead of rewiring the machine that was only supposed to be measured.
## 1. Skill versions
## 1. Collect, four checks in parallel
Run `jsc-hooks/hooks/version-guard.sh report`. It prints `{domain}<TAB>{local}<TAB>{remote}<TAB>{落後|最新|超前|查詢失敗}` per plugin, then `behind<TAB>{count}`.
Start all four collectors at once. They share no data, so serialising them only makes the check four times slower. Each one **MUST run as a sub agent**, and each returns its raw output lines unchanged — no summarising inside the sub agent, because step 2.2 parses those lines.
A report with no `{domain}` row, or one carrying `noregistry<TAB>{path}`, means this CLI has no local plugin registry. Report it as `無法驗證` - never as `最新`. `behind<TAB>0` proves nothing when no domain row precedes it.
Done when all four sub agents have returned, and each has either its raw lines or an explicit failure reason.
Done when every installed domain has a status literal, or the CLI is reported as unverifiable.
### 1.1 Skill versions
## 2. Hook wiring
Run `jsc-hooks/hooks/version-guard.sh report`. It prints `{domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}` per plugin, then `behind<TAB>{count}`.
Run `jsc-hooks/tools/wire-cli.sh status {cli}` for every CLI that `tools/detect-clis.sh` found. Use `status` and nothing else: `wire-cli.sh` without a subcommand rewires, `purge` deletes, and `smoke` executes hooks - all three break the read-only contract.
A report with no `{domain}` row, or one carrying `noregistry<TAB>{path}`, means this CLI has no local plugin registry. Report it as 無法驗證 — never as 最新. `behind<TAB>0` proves nothing when no domain row precedes it.
Exit codes: 0 wired, 1 degraded, 3 skipped because the CLI is not installed, 5 unwired. Each `item` line names one wiring point and whether it is present.
`report` only reads, so it exits 0 even when every lookup fails. Any non-zero exit means the script itself could not run: report the whole version check as 無法驗證 together with the exit code, and let the other three checks finish.
Only claude reaches `wired`. The other four have no pre-tool hook, so `degraded` is their healthy state. Report the degradation reason as-is and never present it as a defect to fix.
Done when every installed domain has a status literal, or the check is reported as unverifiable with its reason.
Done when every detected CLI has a status and its missing items are listed.
### 1.2 Hook wiring
## 3. CLI runtime tests
First run `jsc-cli/tools/detect-clis.sh`. Exit 0 with at least one row → those are the CLIs to check. Exit 0 with no row → report the wiring table as empty and say no AI agent CLI was detected; that is a finding, not a pass. Any non-zero exit → report the wiring check as 無法驗證 with the exit code and stderr.
Run `tools/test-clis.sh` with no CLI arguments. It calls `tools/detect-clis.sh`, then runs real read-only commands for every detected CLI.
Then run `JSC_READONLY=1 jsc-hooks/tools/wire-cli.sh status {cli}` for every detected CLI. Use `status` and nothing else: `wire-cli.sh` without a subcommand rewires, `purge` deletes, and `smoke` executes hooks — all three break the read-only contract.
Output:
| Exit | Meaning | Action |
| --- | --- | --- |
| 0 | wired | Record as wired |
| 1 | degraded | Record as degraded and quote the `reason` text |
| 2 | usage error | Stop this CLI's wiring check. The subcommand or the CLI name is wrong — report it as a defect in this skill, not as a machine fault |
| 3 | skipped | The CLI is not installed. Drop it from the wiring table |
| 5 | unwired | Record every `item` line whose state is `missing` |
| other | unexpected | Report that CLI's wiring as 無法驗證 with the exit code and stderr. Never read it as wired |
- `test<TAB>{cli}<TAB>{test}<TAB>{command}<TAB>{exit-code}<TAB>{verdict}<TAB>{detail}`
- `summary<TAB>{ok}<TAB>{warn}<TAB>{fail}<TAB>{skipped}`
Only claude reaches `wired`. The other four have no pre-tool hook, so `degraded` is their healthy state — report the degradation reason as-is and never present it as a defect to fix.
Verdicts: `ok`, `warn`, `fail`, `skipped`.
Done when every detected CLI carries one of those verdicts and its missing items are listed.
Exit codes: 0 completed, 2 usage error, 3 missing `detect-clis.sh`. Any other script exit code is itself a doctor finding.
### 1.3 Settings, global and project in one scan
Treat `fail` as a machine problem. Treat `warn` as degraded capability: name it in the report, but do not put it in the fix table unless the failing skill needs that feature. Treat `skipped` as no conclusion. Map the report labels to the template as `ok` -> `通過`, `warn` -> `降級`, `fail` -> `失敗`, and `skipped` -> `略過`.
Run `jsc-cli/tools/scan-config.sh scan all` from the current working directory. One call covers both scopes: the spec table is read once and the `scope` column separates the rows. It prints `{項目}<TAB>{範圍}<TAB>{必要}<TAB>{現況}<TAB>{說明}<TAB>{修法}<TAB>{判定}`, closing with `summary<TAB>{missing}<TAB>{invalid}<TAB>{unset}<TAB>{skipped}`.
Done when every detected CLI has at least a version test row and the summary line is read.
| Exit | Action |
| --- | --- |
| 0 | Scan finished. Split the rows by the `範圍` column into a global table and a project table |
| 2 | Usage error. Report it as a defect in this skill and skip the settings check |
| 3 | The spec table is missing. Name the path it looked for and `JSC_CONFIG_SPEC`, then skip the settings check |
| other | Report the settings check as 無法驗證 with the exit code and stderr |
## 4. Global settings
Verdicts: `ok`, `default` (unset, default works), `unset` (optional, feature degrades), `missing` (required, skills break), `invalid` (set but fails verification), `skipped` (offline).
Run `tools/scan-config.sh scan global`. It checks every `scope=global` row of `tools/config-spec.tsv` and prints `item<TAB>scope<TAB>required<TAB>actual<TAB>expect<TAB>fix<TAB>verdict`, closing with `summary<TAB>{missing}<TAB>{invalid}<TAB>{unset}<TAB>{skipped}`.
Add `-o` when Gitea is unreachable; the Gitea-dependent rows then come back `skipped`. Report those rows as 未取得結論 and never as passes.
Verdicts: `ok`, `default`, `unset`, `missing`, `invalid`, `skipped`.
Done when the summary line is read, the rows are split into the two scopes, and every `missing` and `invalid` row is named.
Map the report labels to the template as `ok` -> `通過`, `default` -> `走預設`, `unset` -> `未設定`, `missing` -> `缺漏`, `invalid` -> `設錯`, and `skipped` -> `略過`.
### 1.4 Orphan variables
Add `-o` when Gitea is unreachable; the Gitea-dependent rows then come back `skipped`. Report those rows as inconclusive and never as passes.
Run `jsc-cli/tools/scan-config.sh orphans` — variables used in the source but absent from the spec table. Same exit codes as 1.3; exit 3 here also covers a missing plugins root, so name `JSC_PLUGINS_ROOT` in that case.
Also run `tools/scan-config.sh orphans` - variables used in the source but absent from the spec table. They are a maintenance note for the skill set, not a fault on this machine.
They are a maintenance note for the skill set, not a fault on this machine, so they never enter the 待修項目 table.
Done when the summary line is read and every `missing` and `invalid` row is named.
Done when the orphan list is returned or the check is reported as skipped with its reason.
## 5. Own settings
## 2. Report the five blocks, then build the 待修項目 table
Run `tools/scan-config.sh scan project` from the current working directory. Same output format, `scope=project` rows only.
### 2.1 The five blocks
Say which directory was scanned in the report. A project-scope result is meaningless without it, because the answer changes with every `cd`.
Report all five blocks per `templates/check-page.md`: 技能版本 from 1.1, Hook 接線 from 1.2, 全域設定 and 自我設定 from 1.3's two scopes, and 未登錄變數 from 1.4. Step 1.4 is the only place the orphan list is collected, so leaving it out here drops it from the run entirely — it never enters the 待修項目 table of 2.2, which is exactly why it needs its own block.
When `.env` or `.envrc` exists, name the spec-table variables it overrides and state the value actually in effect. A global setting silently overridden here is the failure this check exists to catch.
The project rows carry the working directory in their heading. A project-scope result is meaningless without it, because the answer changes with every `cd` — so name the directory that step 1.3 scanned, even when the project table is empty.
Done when the scanned directory is stated and every project row has a verdict.
When `.env` or `.envrc` exists in that directory, name the spec-table variables it overrides and state the value actually in effect. A global setting silently overridden here is the failure this check exists to catch.
## 6. Report and record
### 2.2 The 待修項目 table
Report all five tables per `templates/check-page.md`. Then build the `待修項目` table from every `missing`, `invalid`, `unwired` and CLI runtime `fail` item, plus every domain reported `落後`. Order them `missing` -> `invalid` -> `unwired` -> `runtime-fail` -> `落後`. Nothing wrong -> one row reading `無`.
Save each collector's raw lines to a file, then run
`jsc-cli/tools/build-todo.sh --config {scan-all 輸出} --wiring {cli}={status 輸出} --version {report 輸出}`
one `--wiring` per detected CLI. The script merges the three sources and orders them `missing` → `invalid` → `unwired` → `落後`. Nothing wrong → it prints one row whose class reads 無.
| Exit | Action |
| --- | --- |
| 0 | Render the `todo` rows as the 待修項目 table and the `summary` line as 3.2's counts |
| 2 | Usage error. Report it as a defect in this skill; fall back to no 待修項目 table and say the merge did not run |
| 3 | An input file was unreadable. Name the file, rerun that one collector, and say so when it still fails |
| other | Report the merge as failed with the exit code, and keep 2.1's tables on screen |
A check that step 1 reported as 無法驗證 contributes no rows. Say that in the report: an unverified check and a clean check look identical in this table, and only the sentence tells them apart.
Done when all five blocks of 2.1 are on screen with the scanned directory stated — 未登錄變數 included, showing either its rows or the reason 1.4 gave for skipping — and 2.2's 待修項目 table is on screen with its rows in that order, or 2.2's merge failure is reported with its reason while 2.1's blocks stay on screen.
## 3. Record, then hand off
### 3.1 Record
Write the page through `jsc-gitea:wiki`:
- Wiki repo: `jsc-gitea/tools/gitea.sh wiki-repo CHECK`.
- Page name: `CHECK_` plus `gitea.sh hash-id "{hostname}/{user}"` - the host and the login account, not `{owner}/{repo}`. Doctor checks a machine, and it has to work in directories that are not repositories at all.
- Overwrite the whole page. This page type keeps only the latest run.
- Update `CHECK_CONTENTS` from `templates/check-contents.md` in the same pass.
- Page name: `CHECK_` plus `gitea.sh hash-id "{hostname}/{user}"` — the host and the login account, not `{owner}/{repo}`. Doctor checks a machine, and it has to work in directories that are not repositories at all.
- `CHECK_{HASH}` is a **content page**: overwrite the whole page, because this page type keeps only the latest run of this one machine.
- `CHECK_CONTENTS` is a **contents page** and follows the opposite rule: read it back first, then upsert this machine's row from `templates/check-contents.md` in the same pass — add the row if missing, otherwise refresh its 必要項缺漏、設定錯誤、最後體檢 columns. Never overwrite the whole page, and never touch a row belonging to another machine: those rows are other people's records, and this run never read them from anywhere else.
- The `CHECK_CONTENTS` read branches by exit code, and only exit 4 opens the create path. Exit 0 means the page is there, so upsert into what came back. Exit 4 means the page really does not exist yet, so build it from the template. Exit 7 (key invalid or no permission) and exit 8 (any other API failure) both mean the old rows are unknown, never that the page is missing: skip the `CHECK_CONTENTS` write, name the exit code in the report, and create nothing. Writing a fresh template over a directory whose rows were never read wipes every other machine's row, and the write carries no merge and no backup.
`wiki-repo` exiting 3 means no wiki repo is configured for CHECK. Print the tables, skip the wiki write, and put `JSC_WIKI_REPO_CHECK` at the top of the `待修項目` table - that unset variable is itself a finding, so a failed write never fails the health check.
`wiki-repo` exiting 3 means no wiki repo is configured for CHECK. Print the tables, skip the wiki write, and put `JSC_WIKI_REPO_CHECK` at the top of 待修項目 — that unset variable is itself a finding, so a failed write never fails the health check. Any other non-zero exit from `wiki-repo`, `hash-id` or the wiki write is reported the same way: tables on screen, write skipped, exit code named.
Done when either the wiki page URL is reported, or the skipped write is reported together with the reason.
### 3.2 Hand off
## 7. Hand off
State the four counts from 2.2's `summary` line: required items missing, settings invalid, CLIs unwired, domains behind. Recommend `/jsc-cli:setup` when any of those is above zero. Never fix anything here.
State the counts: required items missing, settings invalid, CLIs unwired, CLI runtime failures, domains behind. Recommend `/jsc-cli:setup` when any of those is above zero. Never fix anything here.
Done when the counts are stated and the recommendation is given or explicitly withheld.
Done when either the wiki page URL is reported or the skipped write is reported together with its reason, **and** the four counts are stated with the recommendation given or explicitly withheld.
+30 -6
View File
@@ -1,16 +1,40 @@
---
name: models
description: List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh, so jsc-sdlc gates are enforced in code. States each SDLC stage's required tags: plan and analyze need reasoning-max, implement needs coding, maintain any. Resolves each stage's preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf), for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files.
description: 'List every model usable by each installed AI CLI (claude, codex, copilot, antigravity, kiro) and attach capability tags from references/model-tags.md. Syncs the tag table to $JSC_HOME/model-tags.tsv via tools/model-tags.sh, so jsc-sdlc gates are enforced in code. States each SDLC stage''s required tags: plan and analyze need reasoning-max, implement needs coding, maintain any. Resolves each stage''s preferred model chain via tools/model-config.sh (project .jsc/models overrides $JSC_HOME/models.conf), for switch suggestions only. Use when checking model fitness, inventorying models, or reviewing stage gating; not for switching models or editing the config files.'
---
# models — list CLI models with capability tags
## Steps
1. Run `jsc-cli/tools/detect-clis.sh` to get the installed CLIs. Done when the TSV lists every detected CLI with its executable path.
2. Run `jsc-cli/tools/list-models.sh` to read each CLI's models and the model currently in use. It prints `cli<TAB>model<TAB>in-use` from each CLI's own config, and stays silent for a CLI whose config it cannot read. For every detected CLI it returns no rows for, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). This step **MUST run as a sub agent**. Done when every detected CLI has a model list or is marked unreadable.
1. Start three collectors at once. They read different files and share no state, so the stage preference chain is fetched here rather than waited for at the end.
1. `jsc-cli/tools/detect-clis.sh` — the installed CLIs, as `{name}<TAB>{path}<TAB>{version}`. Exit 0 with at least one row → that is the CLI list. Exit 0 with no row → no AI agent CLI is installed on this machine: report that, name the five it probes, skip steps 2 to 4, and go straight to step 5, because the tag table and the stage requirements are still worth writing out. Any non-zero exit → stop and report the exit code and stderr.
2. `jsc-cli/tools/list-models.sh` — each CLI's models and the model currently in use, as `cli<TAB>model<TAB>in-use`, read from each CLI's own config. It stays silent for a CLI whose config it cannot read and always exits 0; a non-zero exit means the script itself failed, so report the model inventory as 無法取得 with the exit code. This collector **MUST run as a sub agent**.
3. `jsc-cli/tools/model-config.sh list` — one line per stage, `stage<TAB>chain<TAB>source`, with `-` for unconfigured stages. Exit 0 → use the rows in step 6. Exit 2 → usage error, report it as a defect in this skill and show step 6's 階段偏好模型 table as 未取得. Any other exit → same handling, with the exit code named.
Done when all three collectors have returned, and each has either its rows or an explicit failure reason.
2. Reconcile the two lists. For every detected CLI that collector 1.2 returned no rows for, list that CLI's known default models and mark each one with the literal label 「預設推定」 (assumed default). Done when every detected CLI has either a model list from its config or a set of assumed defaults.
3. Attach capability tags to every model per `references/model-tags.md`. A model missing from that table is not tagged by guesswork: add it to the table from the vendor's documentation, or queue it as a `jsc-ask:ask` question. Done when every listed model carries at least one tag and every unlisted model is either added to the table or queued as a `jsc-ask:ask` question.
4. Output a table with four columns: CLI, model, tags, currently in use. Done when the table holds one row per model from step 2.
5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`, and report the path. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists. Done when the command prints the path.
6. Append the SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), and state that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**. Done when all four stages appear with their required tags.
7. Run `jsc-cli/tools/model-config.sh list` and append a 「階段偏好模型」 table right after the stage requirement table, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags. Done when the table shows all four stages, with `-` for unconfigured ones.
5. Run `tools/model-tags.sh sync` to write the tag table to `$JSC_HOME/model-tags.tsv`. This file is what `jsc-hooks/hooks/sdlc-gate.sh` reads, so the SDLC gate stays broken until it exists.
| Exit | Action |
| --- | --- |
| 0 | Report the path it printed |
| 1 | `references/model-tags.md` could not be parsed, or `$JSC_HOME` could not be written, so nothing was written. Name the reference path and the stderr, and state that the SDLC gate stays broken until this is fixed |
| 2 | Usage error — the subcommand or its arguments are wrong, and the script printed its usage line instead of running. Report it as a defect in this skill, and do not retry with a guessed argument. This is the same code the script uses for `UNKNOWN-MODEL` and `UNKNOWN-STAGE`, so it never means a model failed a requirement |
| other | Report the sync as failed with the exit code and stderr. Never report a path that was not printed |
Done when the written path is reported, or the failure is reported with its exit code.
6. Append the two stage tables, in this order.
1. The SDLC stage requirement table (plan and analyze need `reasoning-max`; implement needs `coding`; maintain accepts any), stating that gating is done in code by `sdlc-gate.sh lock {stage}` against the transcript's actual model id — **the models listed here are never allowed to self-assess their own tags**.
2. A 「階段偏好模型」 table right after it, built from collector 1.3's rows, with three columns: stage, chain, source (`project` / `global`). State below the table that the chain does **not** grant passage: it only names the model to suggest switching to when the gate blocks, and expresses preference among models that already satisfy the required tags.
Done when the requirement table shows all four stages with their required tags, and the 階段偏好模型 table shows the same four stages with `-` for unconfigured ones.
+46 -11
View File
@@ -1,6 +1,6 @@
---
name: setup
description: Fix what jsc-cli:doctor found, one confirmed item at a time. Read the 待修項目 table from wiki CHECK_{HASH}, or rebuild it with tools/scan-config.sh and jsc-hooks/tools/wire-cli.sh status when no page exists. Route each item by its fix column - auto writes it through tools/apply-config.sh, ask collects the value through the jsc-ask decision tree first, manual prints the steps for the operator. Delegate compound repairs to their owners: jsc-cli:deploy for a plugin whose version is behind, jsc-hooks:hooks-install for unwired hooks, jsc-cli:models for a missing model-tags.tsv. Re-verify every item after writing and rewrite the CHECK page; use when doctor reports something to fix, not for a read-only checkup.
description: Fix what jsc-cli:doctor found, one confirmed item at a time. Read the 待修項目 table from wiki CHECK_{HASH}, or rebuild it by running the three checkers in parallel and merging them with tools/build-todo.sh. Route each item by its fix column - auto writes it through tools/apply-config.sh, ask collects the value through the jsc-ask decision tree first, manual prints the steps for the operator. Delegate compound repairs to their owners, handing jsc-cli:deploy the mode and the version report it already has. Re-verify every item after writing and rewrite the CHECK page; use when doctor reports something to fix, not for a read-only checkup.
---
# setup — guide or apply the fixes doctor found
@@ -11,15 +11,27 @@ This skill writes. Every write is confirmed first, backed up, and verified after
Read the 待修項目 table from wiki `CHECK_{HASH}` — repo from `jsc-gitea/tools/gitea.sh wiki-repo CHECK`, page name from `gitea.sh hash-id "{hostname}/{user}"`.
No page, or `wiki-repo` exits 3 → rebuild the list here: `tools/scan-config.sh scan all` for settings, `jsc-hooks/tools/wire-cli.sh status {cli}` per detected CLI for wiring, `jsc-hooks/hooks/version-guard.sh report` for versions. Rebuilding **MUST run as a sub agent**.
No page, or `wiki-repo` exits 3, or any other non-zero exit from `wiki-repo`, `hash-id` or the wiki read → rebuild the list here. Rebuilding **MUST run as a sub agent**, and its three checkers **start together**: they read different files and share no state, so serialising them only triples the wait.
| Checker | Command | Exit branching |
| --- | --- | --- |
| Settings | `tools/scan-config.sh scan all` | 0 → use the rows; 2 → usage error, report it as a defect in this skill; 3 → spec table missing, name the path and `JSC_CONFIG_SPEC`; other → report settings as 無法驗證 |
| Wiring | `tools/detect-clis.sh`, then `JSC_READONLY=1 jsc-hooks/tools/wire-cli.sh status {cli}` per detected CLI — this step only takes stock, and `wire-cli.sh` without a subcommand rewires, so the read-only contract is carried in the environment rather than trusted to a correctly typed subcommand | detect-clis exit 0 with no row → no CLI to wire, say so and skip; detect-clis non-zero → report wiring as 無法驗證 with the exit code. Per CLI: 0 wired and 1 degraded → nothing to fix; 2 → usage error, defect in this skill; 3 → CLI not installed, drop it; 5 → collect its `missing` items; 6 → readonly refused the call, which means the subcommand was mistyped into a writing one — nothing on the machine changed; fix the command and rerun that CLI; other → report that CLI as 無法驗證 |
| Versions | `jsc-hooks/hooks/version-guard.sh report` | 0 → use the rows, and treat a report with no `{domain}` row or a `noregistry` line as 無法驗證 — other exits → report versions as 無法驗證 with the exit code |
Merge the three with `tools/build-todo.sh --config {設定輸出} --wiring {cli}={接線輸出} --version {版本輸出}` so the ordering rule lives in one place. Exit 0 → the `todo` rows are the work list; exit 2 → usage error, report it as a defect in this skill; exit 3 → name the unreadable input and rerun that one checker; any other exit → stop and report, because a half-merged list would silently drop a whole class of items.
Keep the version report from that run. Step 3 hands it to `jsc-cli:deploy` instead of making it query again.
State which source the list came from. A stale page and a live scan can disagree, and the operator has to know which one is on screen.
Done when every item carries its scope, verdict and fix route.
Done when every item carries its class, scope, current state and fix route, and the source of the list is named.
## 2. Confirm each item
Ask per the `jsc-ask:ask` decision tree, one item at a time, in the table's order. Every option states its impact scope: which file gets written, which skills start working, what stays broken when skipped.
Ask per the `jsc-ask:ask` decision tree, one item at a time, in the table's order. Confirmation stays strictly sequential: each answer can change what the next item should be, and a batch of questions fired at once takes that away from the operator.
Every option states its impact scope: which file gets written, which skills start working, what stays broken when skipped.
An `ask` item needs its value in the same question — the wiki repo as `{owner}/{repo}`, the Gitea host, the directory path. Never invent one.
@@ -35,31 +47,54 @@ Done when every item is either confirmed with a value or recorded as skipped.
| `auto` on a directory | `tools/apply-config.sh mkdir {PATH}` |
| `ask` | same two commands, with the value the user just gave |
| `manual` | print the exact steps and the file to edit; the operator does it |
| domain 落後 | call `jsc-cli:deploy`, mode `update` |
| domain 落後 | call `jsc-cli:deploy` with mode `update` **and the version report from step 1**, so it neither re-asks the mode nor re-queries the versions |
| hook unwired | call `jsc-hooks:hooks-install` |
| `$JSC_HOME/model-tags.tsv` missing | call `jsc-cli:models` |
`apply-config.sh` exit codes:
| Exit | Action |
| --- | --- |
| 0 | Written. Record the `wrote` or `created` result and the `backup` path |
| 2 | Usage error — the subcommand, the key or the value is wrong. Record the item as 未修好 with that reason, and do not retry with a guessed argument |
| 4 | Backup or write failed, so nothing was written. Record the item as 未修好 and name the rc file and the stderr, then say the machine is unchanged |
| other | Record the item as 未修好 with the exit code and stderr. Never mark it fixed on an unrecognised exit |
`apply-config.sh` writes into the `# jsc-config` block of every existing shell rc file, backs each one up to `$JSC_HOME/backup/config/{timestamp}/` before touching it, and rewrites the block whole. It never edits anything outside that block.
Report the `backup` path it prints. That path is the whole undo story for this run.
Done when every confirmed item has a `wrote`, `created` or delegated result.
Done when every confirmed item has a `wrote`, `created`, delegated or 未修好 result.
## 4. Re-verify
Rerun the check that produced each item — `tools/scan-config.sh scan {scope}` for settings, `wire-cli.sh status {cli}` for wiring, `version-guard.sh report` for versions.
Re-verify every applied item. The items are independent, so **run the re-verifications in parallel** — one batch, one wait. Only the confirmation in step 2 has to stay sequential.
Pick the check by what was actually written, because the two kinds of write become true at different moments:
| What was written | Re-verify with | Why this check |
| --- | --- | --- |
| An environment variable in a shell rc file | `tools/apply-config.sh show`, confirming the `KEY<TAB>VALUE` line is in the `# jsc-config` block | The block is a fact that is already true. The variable reaching the environment is not — a rc file does not touch the running shell |
| A directory | `tools/scan-config.sh scan {scope}`, confirming the row is no longer `missing` or `invalid` | The directory exists the moment it is created |
| Wiring, versions, model tags (delegated) | The owner skill's own returned result | The owner already ran its own verification |
The same exit branching as step 1 applies to `scan-config.sh` and to `apply-config.sh`.
An item that still fails is reported as 未修好 with the reason. Never mark it fixed because the write succeeded: writing the variable and the variable verifying are two different facts.
A newly written rc block does not affect the running shell. Tell the operator to open a new shell or `source` the rc file, and give them the `export` line for the current session. A re-verify that reads the current environment will still show the variable unset — say so rather than reporting a false failure.
For every environment variable written, print the matching `export KEY=VALUE` line for the current session and tell the operator to open a new shell or `source` the rc file. The environment-level proof is handed to the next `/jsc-cli:doctor` run, which starts in a fresh shell — asserting it here would read the shell that could not have picked the value up yet, and report a false failure every time.
Done when every applied item has a fresh verdict from its own checker.
Done when every applied item has a fresh verdict from the checker its own row names, and every environment variable carries its `export` line.
## 5. Record
Rewrite `CHECK_{HASH}` through `jsc-gitea:wiki` with the post-fix state, per `templates/check-page.md`, and refresh the `CHECK_CONTENTS` row. The page keeps only the latest run, so this overwrites the pre-fix picture on purpose.
Rewrite `CHECK_{HASH}` through `jsc-gitea:wiki` with the post-fix state, per `templates/check-page.md`. That page is a **content page** and keeps only the latest run, so this overwrites the pre-fix picture on purpose.
No wiki repo configured → report the tables on screen and say the record was skipped.
`CHECK_CONTENTS` is a **contents page** and gets the opposite treatment: read it back first, then upsert this machine's row per `templates/check-contents.md` — add the row if missing, otherwise refresh its counts and 最後體檢. Never overwrite the whole page, and never touch another machine's row.
The `CHECK_CONTENTS` read branches by exit code, and only exit 4 opens the create path. Exit 0 means upsert into the content that came back. Exit 4 means the page really is not there yet, so build it from the template. Exit 7 (key invalid or no permission) and exit 8 (any other API failure) mean the old rows are unknown, not that the page is missing: skip the `CHECK_CONTENTS` write, name the exit code, and create nothing — the overwrite that is correct for `CHECK_{HASH}` would here destroy every other machine's row, unread and unrecoverable.
No wiki repo configured, or any non-zero exit from the wiki write → report the tables on screen, say the record was skipped, and name the exit code.
Then state the counts: fixed, skipped, delegated, and 未修好. Recommend `/jsc-cli:doctor` for a clean re-check when anything was delegated.
+5 -3
View File
@@ -1,7 +1,9 @@
# 體檢目錄
> 由 `jsc-cli:doctor` 維護。每台執行環境一列;`HASH` 取 `{主機名}/{登入帳號}`,算法與其他頁面共用。
>
> 寫入語意:一列代表一台執行環境,也就是一組主機加帳號。寫入前先讀回整頁,該執行環境已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。體檢頁 `CHECK_{HASH}` 只留最新一次結果、可以整頁改寫,這份目錄頁不行。
| 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | CLI 實測失敗 | 最後體檢 |
| --- | --- | --- | --- | --- | --- | --- |
| [[CHECK_{HASH}]] | {hostname} | {使用者帳號} | {n} | {n} | {n} | {yyyy-MM-dd HH:mm} |
| 體檢頁 | 主機 | 帳號 | 必要項缺漏 | 設定錯誤 | 最後體檢 |
| --- | --- | --- | --- | --- | --- |
| [[CHECK_{HASH}]] | {hostname} | {使用者帳號} | {n} | {n} | {yyyy-MM-dd HH:mm} |
+5 -11
View File
@@ -13,7 +13,6 @@
| --- | --- | --- | --- |
| 技能版本 | {n} | {n} | {n} |
| Hook 接線 | {n} | {n} | {n} |
| CLI 實測 | {n} | {n} | {n} |
| 全域設定 | {n} | {n} | {n} |
| 自我設定 | {n} | {n} | {n} |
@@ -29,12 +28,6 @@
| --- | --- | --- | --- |
| {cli} | {wired、degraded、unwired、skipped} | {項目名,逗號分隔;無則寫「無」} | {降級原因或未偵測到執行檔} |
## CLI 實測
| CLI | 測試 | 指令 | 結束碼 | 判定 | 說明 |
| --- | --- | --- | --- | --- | --- |
| {cli} | {version、help、plugin-list} | {實際命令} | {結束碼} | {通過、降級、失敗、略過} | {輸出摘要或錯誤原因} |
## 全域設定
| 項目 | 必要 | 現況 | 期望 | 修法 | 判定 |
@@ -51,11 +44,12 @@
## 待修項目
> `/jsc-cli:setup` 從這張表接手。沒有待修項目時整張表寫一列「無」。
> 前六欄直接來自 `jsc-cli/tools/build-todo.sh` 的 `todo` 列,順序與類別由那支腳本決定,這裡不另行排序。
> 「影響」欄由 `jsc-cli:doctor` 補上。`/jsc-cli:setup` 從這張表接手;沒有待修項目時腳本會印一列「無」。
| 順序 | 項目 | 範圍 | 判定 | 修法 | 影響 |
| --- | --- | --- | --- | --- | --- |
| {n} | {變數、檔案、hook、CLI 測試或 domain} | {全域、自我、CLI} | {缺漏、設錯、未接線、實測失敗、落後} | {自動、詢問、手動} | {不修的話哪些技能跑不動} |
| 順序 | 類別 | 項目 | 範圍 | 現況 | 修法 | 影響 |
| --- | --- | --- | --- | --- | --- | --- |
| {n} | {missing、invalid、unwired、落後、無} | {變數、檔案、接線項目或 plugin 名} | {global、project、cli 代號、版本} | {實際值、缺少接線的檔案或本機與遠端版本} | {auto、ask、manual 或接手的技能名} | {不修的話哪些技能跑不動} |
## 未登錄變數
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env sh
# build-todo.sh — 把三支檢查腳本的輸出合併成一張「待修項目」表。
#
# /jsc-cli:doctor 與 /jsc-cli:setup 都要這張表,合併規則只留一個真實來源,
# 兩支技能各寫一次就會各自漂移,一邊排序、另一邊漏掉某一類。
#
# 用法:
# build-todo.sh [--config {檔案}]... [--wiring {cli}={檔案}]... [--version {檔案}]...
# 每個選項都可以重複。檔案給「-」代表讀標準輸入(整份只能有一個 -)。
# 三種輸入都省略時視為用法錯誤:空跑會印出「沒有待修項目」,那是假通過。
#
# 輸入格式(由各自的腳本產生,本腳本不自己執行它們,維持唯讀):
# --config jsc-cli/tools/scan-config.sh scan 的輸出
# {項目}<TAB>{範圍}<TAB>{必要}<TAB>{現況}<TAB>{說明}<TAB>{修法}<TAB>{判定}
# 只取判定為 missing 與 invalid 的列,summary 列略過。
# --wiring jsc-hooks/tools/wire-cli.sh status {cli} 的輸出,前面掛上該 CLI 代號。
# 首行 status=... reason=...;其後 item<TAB>{項目}<TAB>{路徑}<TAB>{present|missing}
# 只有 status=unwired 才進待修表,每個 missing 項目一列。
# degraded 是 codex、copilot、antigravity、kiro 的健康狀態,不是缺失。
# --version jsc-hooks/hooks/version-guard.sh report 的輸出
# {domain}<TAB>{本機}<TAB>{遠端}<TAB>{落後|最新|超前|查詢失敗}
# 只取「落後」的列。noregistry 與 behind 列略過。
#
# 輸出(TSV,一行一筆):
# todo<TAB>{序號}<TAB>{類別}<TAB>{項目}<TAB>{範圍}<TAB>{現況}<TAB>{修法}
# summary<TAB>{missing 數}<TAB>{invalid 數}<TAB>{unwired 數}<TAB>{落後數}
# 類別排序固定為 missing、invalid、unwired、落後;同類別內照輸入順序。
# 一項都沒有時仍印一列 todo,類別欄為「無」,呼叫端照樣有東西可以呈現。
#
# 結束碼:0=合併完成(有沒有待修項目都算完成,判斷交給呼叫端)
# 2=用法錯誤(沒給任何輸入、選項寫錯、--wiring 少了 {cli}= 前綴)
# 3=指定的輸入檔讀不到
set -u
TAB=$(printf '\t')
usage() {
[ -n "${WORK:-}" ] && rm -rf "$WORK"
echo "用法:build-todo.sh [--config {檔案}]... [--wiring {cli}={檔案}]... [--version {檔案}]..." >&2
exit 2
}
WORK=$(mktemp -d 2>/dev/null) || { echo "無法建立暫存目錄" >&2; exit 3; }
F_MISSING="$WORK/missing"; F_INVALID="$WORK/invalid"
F_UNWIRED="$WORK/unwired"; F_BEHIND="$WORK/behind"
: > "$F_MISSING"; : > "$F_INVALID"; : > "$F_UNWIRED"; : > "$F_BEHIND"
cleanup() { rm -rf "$WORK"; }
die() { cleanup; echo "$1" >&2; exit "$2"; }
# 輸入檔的存在性先在主 shell 檢查。放進管線裡檢查的話,die 只會結束子 shell,
# 主流程照樣往下跑,最後印出一張少了整類項目卻看起來正常的表。
check_input() { # $1=路徑
[ "$1" = "-" ] && return 0
[ -f "$1" ] || die "讀不到輸入檔:$1" 3
[ -r "$1" ] || die "輸入檔沒有讀取權限:$1" 3
}
# 取得一份輸入的內容。「-」讀標準輸入,其餘一律當檔案路徑。
slurp() { # $1=路徑
if [ "$1" = "-" ]; then cat; else cat "$1"; fi
}
# scan-config.sh scan 的輸出 → missing 與 invalid 兩類。
take_config() { # $1=路徑
slurp "$1" | while IFS="$TAB" read -r key scope req actual desc fix verdict; do
[ -n "${key:-}" ] || continue
[ "$key" = summary ] && continue
case "${verdict:-}" in
missing) printf '%s\t%s\t%s\t%s\n' "$key" "${scope:--}" "${actual:--}" "${fix:--}" >> "$F_MISSING" ;;
invalid) printf '%s\t%s\t%s\t%s\n' "$key" "${scope:--}" "${actual:--}" "${fix:--}" >> "$F_INVALID" ;;
esac
done
}
# wire-cli.sh status 的輸出 → unwired 一類。$1=cli $2=路徑
take_wiring() {
_txt="$WORK/wiring.$$"
slurp "$2" > "$_txt"
# 只有 status=unwired 才是待修。wired 沒事,degraded 是四支非 Claude CLI 的健康狀態,
# skipped 代表那支 CLI 根本沒裝,三者都不該出現在待修表上。
grep -q '^status=unwired' "$_txt" || { rm -f "$_txt"; return 0; }
while IFS="$TAB" read -r kind item path state; do
[ "${kind:-}" = item ] || continue
[ "${state:-}" = missing ] || continue
printf '%s\t%s\t%s\t%s\n' "${item:--}" "$1" "缺少接線:${path:--}" "jsc-hooks:hooks-install" >> "$F_UNWIRED"
done < "$_txt"
rm -f "$_txt"
}
# version-guard.sh report 的輸出 → 落後一類。
take_version() { # $1=路徑
slurp "$1" | while IFS="$TAB" read -r domain local_v remote_v state; do
[ -n "${domain:-}" ] || continue
case "$domain" in behind|noregistry) continue ;; esac
[ "${state:-}" = "落後" ] || continue
printf '%s\t%s\t%s\t%s\n' "jsc-$domain" "版本" "本機 ${local_v:--}、遠端 ${remote_v:--}" "jsc-cli:deploy update" >> "$F_BEHIND"
done
}
got=0
while [ "$#" -gt 0 ]; do
case "$1" in
--config) [ "$#" -ge 2 ] || usage; check_input "$2"; take_config "$2"; got=1; shift 2 ;;
--version) [ "$#" -ge 2 ] || usage; check_input "$2"; take_version "$2"; got=1; shift 2 ;;
--wiring)
[ "$#" -ge 2 ] || usage
case "$2" in *=*) ;; *) usage ;; esac
_cli=${2%%=*}; _file=${2#*=}
[ -n "$_cli" ] && [ -n "$_file" ] || usage
check_input "$_file"; take_wiring "$_cli" "$_file"; got=1; shift 2 ;;
*) usage ;;
esac
done
[ "$got" = 1 ] || usage
n_missing=$(wc -l < "$F_MISSING" | tr -d ' ')
n_invalid=$(wc -l < "$F_INVALID" | tr -d ' ')
n_unwired=$(wc -l < "$F_UNWIRED" | tr -d ' ')
n_behind=$(wc -l < "$F_BEHIND" | tr -d ' ')
seq_no=0
emit_class() { # $1=類別 $2=檔案
while IFS="$TAB" read -r item scope actual fix; do
[ -n "${item:-}" ] || continue
seq_no=$((seq_no + 1))
printf 'todo\t%s\t%s\t%s\t%s\t%s\t%s\n' "$seq_no" "$1" "$item" "$scope" "$actual" "$fix"
done < "$2"
}
emit_class missing "$F_MISSING"
emit_class invalid "$F_INVALID"
emit_class unwired "$F_UNWIRED"
emit_class 落後 "$F_BEHIND"
[ "$seq_no" -gt 0 ] || printf 'todo\t1\t無\t沒有待修項目\t-\t-\t-\n'
printf 'summary\t%s\t%s\t%s\t%s\n' "$n_missing" "$n_invalid" "$n_unwired" "$n_behind"
cleanup
exit 0
+26 -5
View File
@@ -1,5 +1,20 @@
#!/usr/bin/env sh
# check-requires.sh — Check jsc.requires before updating one plugin.
# check-requires.sh — 更新單一 plugin 之前,檢查它宣告的 jsc.requires 最低版本。
# 用法:
# check-requires.sh {claude|codex|copilot|antigravity|kiro} {manifest}
# 輸出(單行,可供程式判讀):
# status=ok reason={沒有宣告相依版本|相依版本符合:...}
# status=blocked reason={缺哪一個 plugin、差哪一版}
# status=unknown reason={manifest 讀不到、不是有效 JSON,或缺 python3}
# 結束碼:0=通過(沒有宣告相依,或全部符合)
# 1=相依版本不符或缺相依 plugin
# 2=用法錯誤(參數個數不對,或 CLI 代號不在五個之內)
# 4=判不出結論(manifest 不存在、不是有效 JSON、缺 python3)
# 1 與 4 分開的理由:兩者都不是「通過」,但成因完全不同。混成同一個碼,呼叫端就只能
# 用同一句話講兩件事,環境壞掉會被說成版本落後,操作者照著去補版本永遠補不到問題點。
# deploy.sh update 在每個 domain 更新前呼叫一次,但擋下不代表跳過:deploy.sh 照樣更新,
# 只印一行提醒說缺哪一版。跳過會讓落後的 domain 永遠更新不到,形成死鎖。
# 真正的阻擋在 jsc-hooks 的 version-guard.sh,技能被叫用時才擋。
set -u
usage() {
@@ -17,8 +32,14 @@ case "$CLI" in
esac
[ -f "$MANIFEST" ] || {
printf 'status=blocked reason=找不到 manifest:%s\n' "$MANIFEST"
exit 1
printf 'status=unknown reason=找不到 manifest:%s\n' "$MANIFEST"
exit 4
}
command -v python3 >/dev/null 2>&1 || {
# 直接讓 shell 回 127 的話,呼叫端會看到一個沒宣告過的結束碼,也讀不到原因。
printf 'status=unknown reason=找不到 python3,無法解析 manifest:%s\n' "$MANIFEST"
exit 4
}
JSC_HOME_DIR="${JSC_HOME:-$HOME/.jsc}"
@@ -38,8 +59,8 @@ try:
with open(manifest, encoding="utf-8") as fh:
data = json.load(fh)
except Exception as exc:
print(f"status=blocked reason=manifest 不是有效 JSON:{manifest}:{exc}")
sys.exit(1)
print(f"status=unknown reason=manifest 不是有效 JSON:{manifest}:{exc}")
sys.exit(4)
requires = ((data.get("jsc") or {}).get("requires") or {})
if not requires:
+1
View File
@@ -33,6 +33,7 @@ JSC_VERSION_GUARD env global no on set ask 設成 off 可完全略過版本前
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_WIKI_REPO_TOOLING env global no JSC_WIKI_REPO wiki-repo ask TOOLING_CONTENTS、TOOLING_{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 傳入的變更檔路徑,註解範圍與繁中編碼守門讀它
1 # config-spec.tsv — jsc 技能組的設定規格表。體檢(/jsc-cli:doctor)與設定(/jsc-cli:setup)共用這一份。
33 JSC_VERSION_TTL
34 JSC_RESTART_GATE
35 JSC_WIKI_REPO_SKILLSET
36 JSC_WIKI_REPO_TOOLING
37 JSC_LANG_GUARD
38 JSC_COMMENT_SCOPE
39 JSC_CHANGED_FILE
+77 -5
View File
@@ -9,11 +9,20 @@
# cmd<TAB>{指令} 即將執行的指令
# exit<TAB>{結束碼}<TAB>{指令} 該指令的結束碼;dry-run 時結束碼印「-」
# skip<TAB>{domain}<TAB>{原因} 本地 clone 是開發中的樹,略過 git pull
# warn<TAB>{domain}<TAB>{原因} 相依版本不符,仍照樣更新的提醒
# compat<TAB>codex<TAB>{舊路徑}<TAB>{新路徑} codex 舊版快取路徑補成指向新版的相容連結
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
# requires<TAB>{domain}<TAB>{檢查結果} update 前的 jsc.requires 檢查
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、note 不算失敗,但呼叫端要據實回報。
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、warn、note 不算失敗,但呼叫端要據實回報。
# update 前每個 domain 都先跑一次同目錄的 check-requires.sh,它的四種結束碼分流如下:
# 0 相依符合,或沒有宣告相依 → 照常更新這個 domain
# 1 相依版本不符或缺相依 plugin
# → 印一行 warn,這個 domain 照樣更新(跳過會讓落後的 domain 永遠更新不到)
# 4 判不出結論(manifest 讀不到、不是有效 JSON、缺 python3)
# → 印一行 note,這個 domain 照樣更新。沒有證據不等於落後,話要跟 warn 分開講
# 2 或其他 檢查腳本自己出錯(用法錯誤或腳本壞掉)→ 印一行 skip,跳過這個 domain 並記為失敗
# marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。
# 各 CLI 的細節都收在這裡,SKILL.md 只描述何時呼叫與參數:
# antigravity 不接受 gitea URL,先 clone 到本地再從路徑安裝,更新時 pull 同一份。
@@ -62,6 +71,48 @@ cli_bin() { # $1=CLI 代號
esac
}
codex_plugin_cache_root() { # $1=plugin name
printf '%s/plugins/cache/jsc/%s\n' "${CODEX_HOME:-$HOME/.codex}" "$1"
}
codex_plugin_versions() { # $1=plugin name
_dir=$(codex_plugin_cache_root "$1")
[ -d "$_dir" ] || return 0
find "$_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec basename {} \; 2>/dev/null | sort -V
}
codex_latest_plugin_dir() { # $1=plugin name $2=required file under version dir
_plugin="$1"
_required="$2"
_dir=$(codex_plugin_cache_root "$_plugin")
[ -d "$_dir" ] || return 1
for _p in "$_dir"/*; do
[ -d "$_p" ] || continue
[ ! -L "$_p" ] || continue
[ -f "$_p/$_required" ] || continue
printf '%s\n' "$_p"
done | sort -V | tail -n1
}
codex_preserve_old_plugin_cache() { # $1=plugin name $2=required file under version dir;stdin=更新前版本清單
[ "$DRYRUN" = 1 ] && return 0
_plugin="$1"
_required="$2"
_new=$(codex_latest_plugin_dir "$_plugin" "$_required" || true)
[ -n "$_new" ] || return 0
_root=$(codex_plugin_cache_root "$_plugin")
while IFS= read -r _version; do
[ -n "$_version" ] || continue
_old="$_root/$_version"
[ "$_old" != "$_new" ] || continue
if [ -e "$_old" ] && [ ! -L "$_old" ]; then
continue
fi
ln -sfn "$_new" "$_old" || continue
printf 'compat\tcodex\t%s\t%s\n' "$_old" "$_new"
done
}
usage() {
echo "用法:deploy.sh [-n] {install|update|uninstall} {claude|codex|copilot|antigravity|kiro} {domain} [domain...]" >&2
exit 2
@@ -92,7 +143,12 @@ restart_gate_sh() {
return 1
}
check_requires() { # $1=domain;0=可更新,1=略過這個 domain
# 相依版本不符只回報、不跳過。跳過的話,落後的 domain 永遠等不到那一版相依,
# 也就永遠更新不到,兩個 domain 互相等就形成死鎖。阻擋改放在技能呼叫那一層:
# jsc-hooks 的 version-guard.sh 會在版本不足時擋下該 domain 的技能,更新照跑不會壞事。
# 檢查腳本自己出錯(退出碼 2 以上:用法錯誤或腳本壞掉)是另一回事,那不是相依不符,
# 讀不到結論就不能當成通過,照舊記 FAILED 並跳過這個 domain。
check_requires() { # $1=domain;0=繼續更新,1=略過這個 domain(只有檢查腳本自己出錯才會回 1)
[ "$MODE" = update ] || return 0
sync_local "$1"
manifest="$LOCAL_DIR/$1/plugin.json"
@@ -103,8 +159,15 @@ check_requires() { # $1=domain;0=可更新,1=略過這個 domain
return 0
fi
if [ "$code" -eq 1 ]; then
printf 'skip\t%s\t%s\n' "$1" "相依版本不符,未更新:$out"
return 1
printf 'warn\t%s\t%s\n' "$1" "相依版本不符,這次照樣更新;補齊相依版本以前,叫用這個 domain 的技能會被 jsc-hooks 的 version-guard.sh 擋下。要補的版本:$out"
return 0
fi
# 判不出結論跟版本落後要講不同的話。把兩者混成同一句,環境壞掉會被說成版本落後,
# 操作者照著去補版本永遠補不到問題點。照樣更新的理由與 version-guard.sh 一致:
# 沒有證據不等於落後,缺基礎設施就停掉更新,等於讓環境永遠修不好。
if [ "$code" -eq 4 ]; then
printf 'note\t%s\t%s\n' "$1" "判不出相依版本,這次照樣更新;成因不是版本落後,先修環境再重跑檢查:$out"
return 0
fi
FAILED=1
printf 'skip\t%s\t%s\n' "$1" "相依版本檢查失敗,未更新:$out"
@@ -220,8 +283,17 @@ deploy_codex() {
for d in $DOMAINS; do run "$bin" plugin add "jsc-$d@jsc"; done
;;
update)
_old_cli_versions=$(codex_plugin_versions jsc-cli)
_old_hooks_versions=$(codex_plugin_versions jsc-hooks)
run "$bin" plugin marketplace upgrade jsc
for d in $DOMAINS; do check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc"; done
for d in $DOMAINS; do
check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc"
if [ "$d" = cli ]; then
printf '%s\n' "$_old_cli_versions" | codex_preserve_old_plugin_cache jsc-cli tools/check-requires.sh
elif [ "$d" = hooks ]; then
printf '%s\n' "$_old_hooks_versions" | codex_preserve_old_plugin_cache jsc-hooks hooks/session-timer.sh
fi
done
;;
uninstall)
for d in $DOMAINS; do run "$bin" plugin remove "jsc-$d@jsc"; done
+3
View File
@@ -2,6 +2,9 @@
# detect-clis.sh — 找出已安裝的 AI CLI 工具與執行檔路徑。
# 輸出(TSV): name<TAB>path<TAB>version(未安裝的不輸出)
# 支援: claude / codex / copilot / antigravity(agy) / kiro
# 結束碼: 0=一律回 0。probe 找不到執行檔就跳過那一列,不算失敗,所以沒有其他碼。
# 一台機器一個 CLI 都沒裝也是 0,只是不輸出任何一列。呼叫端要看輸出有幾列,
# 不要拿結束碼判斷有沒有裝到 CLI。
set -u
probe() { # $1=顯示名 $2=執行檔名 $3=版本參數
p=$(command -v "$2" 2>/dev/null) || return 0
+3
View File
@@ -10,6 +10,9 @@
# kiro ~/.kiro/settings/cli.json,退回 ~/.config/kiro/settings.json
# 五個 CLI 都沒有「列出可用模型」的指令,所以清單只到設定檔寫出來的模型。
# 沒有列的 CLI 由 skills/models 的 sub agent 補上預設模型並標註「預設推定」。
# 結束碼: 0=一律回 0。設定檔不存在、讀不到或裡面沒寫模型,都只是少掉那個 CLI 的列,
# 不算失敗,所以沒有其他碼。呼叫端要看輸出有沒有該 CLI 的列,
# 不要拿結束碼判斷讀不讀得到設定檔。
set -u
# 取第一個存在的檔案;都不存在就不印。
+3 -1
View File
@@ -10,6 +10,8 @@
# model-config.sh get {stage} 印出該階段解析後的模型鏈(逗號分隔);未設定不印。兩種情況都 exit 0。
# model-config.sh list 每階段一行:stage<TAB>chain<TAB>source(project 或 global);未設定的 chain 與 source 印「-」。
# model-config.sh resolve {stage} 印出該階段「目前 CLI 可用的第一個模型」單一名稱;鏈為空或無法判定時不印,exit 0。
# 結束碼:0=查詢完成(查得到、查不到都算完成,判斷交給呼叫端)
# 2=用法錯誤(子命令不認得,或階段不在 plan、analyze、implement、maintain 之內)
set -u
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
@@ -83,7 +85,7 @@ resolve_first_usable() { # $1=階段
usage() {
echo "用法:model-config.sh get {plan|analyze|implement|maintain} | list | resolve {plan|analyze|implement|maintain}" >&2
exit 1
exit 2
}
case "${1:-}" in
+8 -1
View File
@@ -16,6 +16,13 @@
# stage<TAB>{階段}<TAB>{必要標籤以逗號分隔,不限標籤時為 any}
# model<TAB>{模型鍵}<TAB>{能力標籤以逗號分隔}
#
# 全域結束碼(每個子命令都照這一套,呼叫端只要認碼就好):
# 0 通過或正常完成
# 1 模型缺標籤(gate 印 FAIL:{標籤}),或 sync 解析不到對照表因而沒寫出檔案
# 2 用法錯誤:子命令或參數不對(含 UNKNOWN-MODEL 與 UNKNOWN-STAGE 這兩種「無法判定」)
# 「模型不合格」與「這支腳本被叫錯」刻意分成 1 與 2 兩碼:兩者共用 exit 1 的話,呼叫端會把
# 自己打錯的指令讀成「這個模型不夠格」,把好模型默默丟掉,而且錯在哪永遠不會浮出來。
#
# gate 的輸出與 exit code:
# PASS exit 0 模型具備該階段全部必要標籤
# FAIL:{缺少的標籤} exit 1 模型缺標籤,不得執行該階段
@@ -131,7 +138,7 @@ gate() { # $1=階段 $2=模型 id
usage() {
echo "用法:model-tags.sh dump | sync | stage {階段} | model {模型 id} | gate {階段} {模型 id}" >&2
exit 1
exit 2
}
case "${1:-}" in
-158
View File
@@ -1,158 +0,0 @@
#!/usr/bin/env sh
# test-clis.sh — 實際呼叫已安裝的 AI CLI,找出靜態設定看不出的環境問題。
# 用法:
# test-clis.sh [cli...]
# 輸出(TSV):
# test<TAB>{cli}<TAB>{test}<TAB>{指令}<TAB>{結束碼}<TAB>{判定}<TAB>{說明}
# summary<TAB>{ok}<TAB>{warn}<TAB>{fail}<TAB>{skipped}
# 判定:
# ok 實際命令成功
# warn CLI 可用,但該功能在這支 CLI 或這個版本不是必要功能
# fail 命令失敗、逾時,或必要功能不存在
# skipped 未安裝或無法執行該項測試
# 結束碼:0=測試完成;2=用法錯誤;3=找不到 detect-clis.sh
set -u
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
DETECT="$HERE/detect-clis.sh"
TIMEOUT_SECONDS="${JSC_CLI_TEST_TIMEOUT:-10}"
ok_count=0
warn_count=0
fail_count=0
skipped_count=0
[ -f "$DETECT" ] || { echo "找不到 detect-clis.sh:$DETECT" >&2; exit 3; }
usage() {
echo "用法:test-clis.sh [claude|codex|copilot|antigravity|kiro ...]" >&2
exit 2
}
cli_bin() {
case "$1" in
antigravity) printf 'agy' ;;
kiro) printf 'kiro-cli' ;;
claude|codex|copilot) printf '%s' "$1" ;;
*) return 1 ;;
esac
}
emit() { # cli test command code verdict detail
printf 'test\t%s\t%s\t%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" "$5" "$6"
case "$5" in
ok) ok_count=$((ok_count + 1)) ;;
warn) warn_count=$((warn_count + 1)) ;;
fail) fail_count=$((fail_count + 1)) ;;
skipped) skipped_count=$((skipped_count + 1)) ;;
esac
}
run_capture() { # $@=command
out_file=$(mktemp) || return 125
err_file=$(mktemp) || { rm -f "$out_file"; return 125; }
if command -v timeout >/dev/null 2>&1; then
timeout "$TIMEOUT_SECONDS" "$@" >"$out_file" 2>"$err_file"
else
"$@" >"$out_file" 2>"$err_file"
fi
code=$?
text=$(cat "$out_file" "$err_file" 2>/dev/null | tr '\r\n\t' ' ' | sed 's/[[:space:]][[:space:]]*/ /g; s/^ //; s/ $//; s/.*TOKEN[^ ]*/[secret]/g' | cut -c1-180)
rm -f "$out_file" "$err_file"
RUN_CODE=$code
RUN_TEXT=${text:-無輸出}
return 0
}
run_required() { # cli test command...
cli=$1
test_name=$2
shift 2
cmd_text="$*"
run_capture "$@"
code=$RUN_CODE
detail=$RUN_TEXT
if [ "$code" -eq 0 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
elif [ "$code" -eq 124 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
else
emit "$cli" "$test_name" "$cmd_text" "$code" fail "$detail"
fi
}
run_optional() { # cli test command...
cli=$1
test_name=$2
shift 2
cmd_text="$*"
run_capture "$@"
code=$RUN_CODE
detail=$RUN_TEXT
if [ "$code" -eq 0 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" ok "$detail"
elif [ "$code" -eq 124 ]; then
emit "$cli" "$test_name" "$cmd_text" "$code" fail "命令逾時(${TIMEOUT_SECONDS} 秒)"
else
emit "$cli" "$test_name" "$cmd_text" "$code" warn "$detail"
fi
}
has_cli() {
name=$1
"$DETECT" | awk -F '\t' -v name="$name" '$1 == name { found = 1 } END { exit found ? 0 : 1 }'
}
test_one() {
cli=$1
bin=$(cli_bin "$cli") || usage
path=$(command -v "$bin" 2>/dev/null || true)
if [ -z "$path" ]; then
emit "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
return 0
fi
case "$cli" in
antigravity)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help
run_required "$cli" plugin-list "$path" plugin list
;;
kiro)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help-all
run_optional "$cli" plugin-list "$path" plugin list
;;
*)
run_required "$cli" version "$path" --version
run_optional "$cli" help "$path" --help
run_required "$cli" plugin-list "$path" plugin list
;;
esac
}
if [ "$#" -eq 0 ]; then
set -- $("$DETECT" | cut -f1)
fi
if [ "$#" -eq 0 ]; then
emit all executable "-" "-" skipped "未偵測到任何支援的 CLI"
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
exit 0
fi
for cli in "$@"; do
case "$cli" in
claude|codex|copilot|antigravity|kiro)
if has_cli "$cli"; then
test_one "$cli"
else
bin=$(cli_bin "$cli") || usage
emit "$cli" executable "$bin" "-" skipped "未偵測到執行檔"
fi
;;
*) usage ;;
esac
done
printf 'summary\t%s\t%s\t%s\t%s\n' "$ok_count" "$warn_count" "$fail_count" "$skipped_count"
exit 0